Go红队开发—格式导出

文章目录

  • 输出功能
    • CSV输出
      • CSV 转 结构体
      • 结构体 转 CSV
      • 端口扫描结果使用CSV格式导出
    • HTML输出
    • Sqlite输出
      • nmap扫描
    • JSON
      • map转json
      • 结构体转json
      • json写入文件
      • json编解码
      • json转结构体
      • json转map
      • json转string
      • 练习:nmap扫描结果导出json格式

输出功能

在我们使用安全工具的时候基本都会有一个输出功能,同样也很重要,所以下面介绍csv、json、html、sqlite的输出格式。

CSV输出

下载包:go get -u github.com/gocarina/gocsv

使用之前先明确我们要csv格式干什么:

  • 首先一些数据可能就是存在csv文件里面,需要我们提取出来的话就需要另外写函数,但是现在有现成的包使用就很方便了
  • 其次我们使用一些安全工具的时候经常会有导出格式为csv格式的,所以在开发过程中也是一个很重要的需求,使用Gocsv包会很方便
    同理我们往后的其他格式也一样的需求。

CSV 转 结构体

test.csv文件内容为:

1.在CSV转结构体的时候,我们需要构造一个结构体,用来接收CSV文件中的表头

type Person struct {Id   string `csv:"id"`Name string `csv:"name"`Age  int    `csv:"age"`}

2.解析csv文件

// 解析CSV文件func anlyzeCSV() {file, err := os.OpenFile("test.csv",os.O_RDWR|os.O_CREATE,0666,)defer file.Close()if err != nil {fmt.Println("打开文件失败:", err)}person := []*Person{}if err := gocsv.UnmarshalFile(file, &person); err != nil { //UnmarshalFile将文件解析为结构体fmt.Println("解析文件失败:", err)}fmt.Println("id,name,age")for _, p := range person {fmt.Println(p.Id, p.Name, p.Age)}}

结构体 转 CSV

// 写入CSV文件func writeCSV() {file, err := os.OpenFile("test.csv", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)if err != nil {fmt.Println("打开文件失败:", err)}defer file.Close()person := []*Person{}person = append(person, &Person{Id:   "1",Name: "李四",Age:  20,})err = gocsv.MarshalFile(&person, file) //MarshalFile将数据写入文件if err != nil {fmt.Println("写入文件失败:", err)}}

端口扫描结果使用CSV格式导出

结合之前的端口扫描练习,将结果通过所学的知识CSV格式输出
这里我对之前的练习进行了一个改造,就是让输出美观一点,同时修改了传参与返回值,目的都是为了拿到扫描完成的端口结果。
为了写进csv还创建了一个结构体。


函数中大部分代码其实都是格式的转换,主要功能其实学会了上面基本都能做了。

所以这里我在放代码前说一下我遇到的问题:

  • csv的表头要修改的话是比较困难,我没有找到一个比较好的办法,就是在写入csv的代码过程中,修改csv表头,我的解决办法是通过开一个新的结构体,通过老结构体的数据传递到新的结构体中就能够修改csv头写进文件里了(这里有大佬知道解决办法可以告诉我一下)
  • 时间格式化有问题,时间格式一定只能用他给出的几种时间:- "2006-01-02 15:04:05":表示 年-月-日 时:分:秒 格式,如果你修改一下2006年改为2002年都是会格式化出错,这一点尝试了几回发现原来是格式时间数字也固定的表达的。
  • 你要写入的结构体的变量名首个字母要大写,一定要大写,否则他会报错,可能这也是一种规范吧,反正不大写就会报错。 最后的那个示例代码你可以尝试把HostPort结构体中的ScanTime修改首字母小写scanTime,很好的验证了小写的时候出现的错误,当然我运行的时候没有报错,但是他实际上是他没有吧你的时间写进csv中,也代码出错了,可以观察验证一下确实不能小写只能首字母大写。
  • 自定义格式实现MarshalCSV接口后,在写入的时候会自动调用该函数,你可以在该函数进行一些初始化或者格式化动作等等。

这补充一下时间格式化的代码:

// 格式化时间time.Time类型func timeFormat(t time.Time) string {return t.Format("2006年1月2日") //转为string类型}// Parse将string类型转为time.Time类型func timeParse(t string) time.Time {tm, _ := time.Parse("2006-01-02 15:04:05", t)return tm}

代码示例:(成功将扫描结果存到csv中保存)


// 稍微改一下代码,return一个port回来,然后输出到csv中func start_WaitGroup_scan_port(host string) ([]int, time.Time) {var (wg      sync.WaitGroupch      = make(chan int, 1024) // 增加缓冲区,减少阻塞count   intworkers = 100 // 控制并发数)var scanPort = func(hostname string, port int) {defer wg.Done()address := fmt.Sprintf("%s:%d", hostname, port)conn, err := net.DialTimeout("tcp", address, 2*time.Second)if err == nil {conn.Close()ch <- port}}// 控制并发数sem := make(chan int, workers)for i := 0; i < 65536; i++ {wg.Add(1)sem <- 1go func(port int) {defer func() { <-sem }()scanPort(host, port)}(i)}go func() {wg.Wait()close(ch)}()ports := []int{}for port := range ch {//fmt.Printf("open: %d\n", port)ports = append(ports, port) //开放端口添加进去count++}fmt.Printf("-------------------------- host:%v --------------------------------\n", host)fmt.Println("扫描完成,共开放端口:", count)fmt.Println("开放端口:", ports)t := time.Now()fmt.Println("时间:", timeFormat(t))fmt.Println("------------------------------------------------------------------")return ports, t}// 自定义格式type myTime struct {time.Time}// 当你的自定义类型实现了这个接口后,在csv写入的时候会自动帮你格式化func (m *myTime) MarshalCSV() (string, error) {return m.Time.Format("2006-01-02 15:04:05"), nil}// 保存扫描的主机和端口type HostPort struct {Host     string `csv:"Host"`Ports    string `csv:"Ports"`ScanTime myTime `csv:"Time"`}func scanhost(host []string) []*HostPort {var ports []int     //接收扫描端口结果var t time.Time     //接收扫描结束时间plist := []string{} //接收每一个ip扫描的端口列表for _, h := range host {ports, t = start_WaitGroup_scan_port(h)for _, p := range ports {plist = append(plist, strconv.Itoa(p)) //strconv.Itoa将int转为string,添加进列表里面}}hostports := []*HostPort{}for _, h := range host {hostports = append(hostports, &HostPort{Host:     h,Ports:    "=" + strings.Join(plist, ","), //将列表转为字符串,用逗号分隔ScanTime: myTime{Time: t},                //自动格式化不用担心,因为实现了MarshalCSV方法})}return hostports}// 输出结果到csv中func outputHostPortCSV(hostports []*HostPort) {file, err := os.OpenFile("host_port.csv", os.O_RDWR|os.O_CREATE, 0666)defer file.Close()if err != nil {fmt.Println("打开文件失败:", err)return}err = gocsv.MarshalFile(&hostports, file)if err != nil {fmt.Println("写入文件失败:", err)return}fmt.Println("写入成功")}// 格式化时间time.Time类型func timeFormat(t time.Time) string {return t.Format("2006年1月2日") //转为string类型}// Parse将string类型转为time.Time类型func timeParse(t string) time.Time {tm, _ := time.Parse("2006-01-02 15:04:05", t)return tm}func main() {//anlyzeCSV()//writeCSV()//timeFormat()//timeFormat(time.Now())//timeParse("2024-11-5 5:40:43")//anlyzeAlipay()outputHostPortCSV(scanhost([]string{"127.0.0.1"})) //这里可以通过参数来给一个ip列表,具体操作可以按自己需求来}

到这里不知道各位是否觉得逐渐有点安全工具内味了。

HTML输出

在html模板中就比较简单了,将结果传导模板渲染即可
这里我个人没遇到什么问题,拿来就用了

template.html文件直接复制就行,无所谓的,主要是看{{range .}}这个意思是循环,{{end}}表示循环结束,要输出你的结构体中的变量就是用{{.xxx变量名}}

{{range .}}
<tr><td style="word-wrap:break-word;word-break:break-all;">{{.Host}}</td><td style="word-wrap:break-word;word-break:break-all;">{{.Ports}}</td><td style="word-wrap:break-word;word-break:break-all;">{{.ScanTime}}</td>
</tr>
{{end}}

go代码中就主要用两个函数执行,你拿到数据之后无非就是渲染数据到html文件中:

ParseFiles获取模板文件
Execute执行渲染
  • 有一个无关紧要的细节:创建项目目录的时候不要和某些包重名,大小写不一样也算重名,重名了就无法使用你要导入的包了。


template.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>扫描结果</title><style>body {font-family: 'Courier New', Courier, monospace;background-color: #0d0d0d;color: #00ff00;margin: 40px;}h1 {text-align: center;color: #00ff00;font-size: 2.5em;margin-bottom: 30px;}table {width: 100%;border-collapse: collapse;margin-bottom: 30px;border: 2px solid #00ff00;}th, td {padding: 12px;text-align: left;border: 1px solid #00ff00;}th {background-color: #1a1a1a;font-size: 1.2em;color: #00ff00;}tr:nth-child(even) {background-color: #1a1a1a;}tr:nth-child(odd) {background-color: #0d0d0d;}tr:hover {background-color: #262626;}</style></head><body><h1>扫描结果</h1><table><tr><th>Host</th><th>Ports</th><th>Time</th></tr>{{range .}}<tr><td style="word-wrap:break-word;word-break:break-all;">{{.Host}}</td><td style="word-wrap:break-word;word-break:break-all;">{{.Ports}}</td><td style="word-wrap:break-word;word-break:break-all;">{{.ScanTime}}</td></tr>{{end}}</table></body></html>

参考代码:

package mainimport ("fmt""html/template""net""os""strconv""strings""sync""time")// 格式化时间time.Time类型func timeFormat(t time.Time) string {return t.Format("2006-01-02 15:04:05") //转为string类型}type HostPort struct {Host     stringPorts    stringScanTime time.Time}// 这里就和csv不同了,就需要自己写一个函数重载调用,这里是用来格式化时间func (r *HostPort) Time() string {return r.ScanTime.Format("2006-01-02 15:04:05")}// 稍微改一下代码,return一个port回来,然后输出到csv中func start_WaitGroup_scan_port(host string) ([]int, time.Time) {var (wg      sync.WaitGroupch      = make(chan int, 1024) // 增加缓冲区,减少阻塞count   intworkers = 100 // 控制并发数)var scanPort = func(hostname string, port int) {defer wg.Done()address := fmt.Sprintf("%s:%d", hostname, port)conn, err := net.DialTimeout("tcp", address, 2*time.Second)if err == nil {conn.Close()ch <- port}}// 控制并发数sem := make(chan int, workers)for i := 0; i < 65536; i++ {wg.Add(1)sem <- 1go func(port int) {defer func() { <-sem }()scanPort(host, port)}(i)}go func() {wg.Wait()close(ch)}()ports := []int{}for port := range ch {//fmt.Printf("open: %d\n", port)ports = append(ports, port) //开放端口添加进去count++}fmt.Printf("-------------------------- host:%v --------------------------------\n", host)fmt.Println("扫描完成,共开放端口:", count)fmt.Println("开放端口:", ports)t := time.Now()fmt.Println("时间:", timeFormat(t))fmt.Println("------------------------------------------------------------------")return ports, t}func scanhost(host []string) []*HostPort {var ports []int     //接收扫描端口结果var t time.Time     //接收扫描结束时间plist := []string{} //接收每一个ip扫描的端口列表for _, h := range host {ports, t = start_WaitGroup_scan_port(h)for _, p := range ports {plist = append(plist, strconv.Itoa(p)) //strconv.Itoa将int转为string,添加进列表里面}}hostports := []*HostPort{}for _, h := range host {hostports = append(hostports, &HostPort{Host:     h,Ports:    strings.Join(plist, ","), //将列表转为字符串,用逗号分隔ScanTime: t,                        //自动格式化不用担心,因为实现了MarshalCSV方法})}return hostports}func anlyzeHtml() {temphtml, err := template.ParseFiles("template.html")if err != nil {fmt.Println("打开模版失败", err)return}file, err := os.Create("output.html")defer file.Close()if err != nil {fmt.Println("创建文件失败:", err)return}defer file.Close()err = temphtml.Execute(file, scanhost([]string{"127.0.0.1"}))if err != nil {fmt.Println("渲染失败:", err)return}fmt.Println("html结果导出成功!")}func main() {anlyzeHtml()}

Sqlite输出

下载包

go get github.com/mattn/go-sqlite3

导入包的时候注意细节

github.com/mattn/go-sqlite3 导入包需要给一个匿名重命名一下
因为go-sqlite3 包在导⼊时会执⾏其 init 函数,该函数会注册 SQLite3 驱动到 database/sql 包中,所以为了使⽤ sql.Open(“sqlite3”, …) 时,database/sql 包就能够找到并使⽤这个驱动就跟着做就行了。

_ "github.com/mattn/go-sqlite3"

在涉及到数据库的时候无非就是几件事情

  • 打开数据库连接
  • 写sql语句
  • 执行sql语句
  • 关闭连接

同理下面就按照这个顺序介绍

打开数据库连接(test.db不存在他会帮你创建的,不用担心)

db, err := sql.Open("sqlite3", "./test.db")if err != nil {fmt.Println("连接失败", err)}//关闭数据库在这里,//但是因为使用了go中的defer所以他会自动帮你关闭连接defer db.Close()  

写sql语句执行语句
创建表:users表为例

createTableSQL := CREATE TABLE IF NOT EXISTS users (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT,
"age" INTEGER
);_, err = db.Exec(createTableSQL)
if err != nil {fmt.Println("创建数据库失败:", err)
}

插入:参数值可以有两种方式给:

  • 第一种:直接给值在sql语句中
insertSQL = `INSERT INTO users (name,age) VALUES ("李四",15)`
db.Exec(insertSQL)
  • 第二种:可以通过占位符 ? 在执行语句的时候传递参数值
insertSQL := `INSERT INTO users (name, age) VALUES (?,?)` //可以通过??作为占位符,exec的时候就可以传参的方式传进去
_, err = db.Exec(insertSQL, "张三", 18)
if err != nil {fmt.Println("插入数据失败:", err)
}

补充一些细节:

  • 如果你sql执行了,代码运行没有报错,但是你在数据库中仍然没有看到变化,那大概率是你sql语句写错了。
  • sqlite在go中不用安装什么软件,直接使用即可,我个人是使用vscode中的sqlite插件查看数据的,下图中我两个插件都安装了,第一个安装完成后你在项目中点开db文件就能直接看到数据被解析可以看到内容了。如果你使用其他编辑器的话自行搜索方法打开即可,推荐https://www.navicat.com.cn/products/navicat-premium/

nmap扫描

这里我将之前自定义的端口扫描换成nmap扫描了

1.第一步:需要提前安装好nmap:
https://nmap.org/download.html
安装对应系统的版本后他会自己添加到系统变量中的,比如我windows安装完毕后再cmd窗口输入nmap就可以有提示出来了,如果没有自己就去安装的路径,将该路径复制到环境变量中去。(这里自行解决)

2.第二步:下载go-nmap
注意了,这个是辅助包,不包含nmap的,nmap前面我们已经安装了
(当然如果你要不安装nmap就使用的话也有对应的包是下载来就是go语言写的nmap: github.com/Ullaakut/nmap 库 ,这个可以解决你的需求,但是功能肯定没有nmap强大)

go get github.com/lair-framework/go-nmap

主要执行的还是调用我们的命令
注意:-oX 如果你不加没有报错的话就可以不用加,这涉及到的输出问题,如果输出对不上他总是报错,解决办法我只有这一个,有大佬有其他解决办法可以告诉我一下。

cmd := exec.Command("nmap", "-sV", "-T4", "-oX", "-", target) // -sV:服务探测,-T4:扫描速度

解析nmap的结果

result, err := nmap.Parse(output)
if err != nil {log.Fatalf("解析失败: %v", err)
}

重点是打印,对应的变量名也写的很清楚了

// 打印结果
for _, host := range result.Hosts {fmt.Printf("主机: %s\n", host.Addresses[0].Addr)for _, port := range host.Ports {fmt.Printf(" 端口 %d/%s: %s %s\n",port.PortId,port.Protocol,port.Service.Name,port.Service.Product)}}

运行后的结果与db数据库


示例代码:

package mainimport ("database/sql""fmt""log""os/exec""time""github.com/lair-framework/go-nmap"_ "github.com/mattn/go-sqlite3")// 测试sqlitefunc testSqlite() {db, err := sql.Open("sqlite3", "./test.db")if err != nil {fmt.Println("连接失败", err)}defer db.Close()// //创建表createTableSQL := `CREATE TABLE IF NOT EXISTS users ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"name" TEXT,"age" INTEGER);`_, err = db.Exec(createTableSQL)if err != nil {fmt.Println("创建数据库失败:", err)}//插入数据insertSQL := `INSERT INTO users (name, age) VALUES (?,?)` //可以通过??作为占位符,exec的时候就可以传参的方式传进去_, err = db.Exec(insertSQL, "张三", 18)if err != nil {fmt.Println("插入数据失败:", err)}insertSQL = `INSERT INTO users (name,age) VALUES ("李四",15)`db.Exec(insertSQL)insertSQL = `INSERT INTO users (name,age) VALUES ("王五",23)`db.Exec(insertSQL)insertSQL = `INSERT INTO users (name,age) VALUES ("test1",23)`db.Exec(insertSQL)insertSQL = `INSERT INTO users (name,age) VALUES ("test1",23)` //插入两个test1作为测试数据db.Exec(insertSQL)insertSQL = `INSERT INTO users (name,age) VALUES ("test2",23)`db.Exec(insertSQL)//更新数据updateSQL := `UPDATE users SET age = ? WHERE id = ?`db.Exec(updateSQL, 45, 1)                                    //更新id为1的年龄为45,即张三的年龄更改为45updateSQL = `UPDATE users SET name = "五福" where name = "王五"` //将所有叫王五的人更改名字为五福db.Exec(updateSQL)// //删除数据// //删除name为test的数据deleteSQL := `DELETE FROM users WHERE name = ?`res, err := db.Exec(deleteSQL, "test1")if err != nil {fmt.Println("删除失败", err)}//查看删除了多少个数据resRows, err := res.RowsAffected()if err != nil {fmt.Println("删除失败:", err)}fmt.Println("更新了:", resRows)}// 使用nmap扫描return结果func nmapScan(target string) (*nmap.NmapRun, time.Time) {// 执行Nmap扫描cmd := exec.Command("nmap", "-sV", "-T4", "-oX", "-", target) // -sV:服务探测,-T4:扫描速度output, err := cmd.CombinedOutput()if err != nil {log.Fatalf("Nmap扫描失败: %v\n输出: %s", err, string(output))}// 解析Nmap输出result, err := nmap.Parse(output)if err != nil {log.Fatalf("解析失败: %v", err)}// 打印结果for _, host := range result.Hosts {fmt.Printf("主机: %s\n", host.Addresses[0].Addr)for _, port := range host.Ports {fmt.Printf(" 端口 %d/%s: %s %s\n",port.PortId,port.Protocol,port.Service.Name,port.Service.Product)}}return result, time.Now()}// 将结果写进sqlite中func outpuSqlite(res *nmap.NmapRun, t time.Time) {db, err := sql.Open("sqlite3", "./test.db")if err != nil {fmt.Println("连接失败", err)}defer db.Close()createTableSQL := `CREATE TABLE IF NOT EXISTS result ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"ip_address" TEXT,"port" INTEGER,"protocol" TEXT,"severity" TEXT,"timestamp" DATETIME);`db.Exec(createTableSQL)//将扫描结果插入数据库中for _, host := range res.Hosts {for _, port := range host.Ports {insertSQL := `INSERT INTO result (ip_address, port, protocol, severity, timestamp) VALUES (?,?,?,?,?)`//添加数据,注明:hight是我随便编的,可以搞一个对照表来确定是否高危端口db.Exec(insertSQL, host.Addresses[0].Addr, port.PortId, port.Protocol, "hight", t.Format("2006年01月02日"))}}}func main() {//nmapScan()res, t := nmapScan("baidu.com") //扫描单个目标outpuSqlite(res, t)}

JSON

爆肝json篇章…


没啥好说过了Sqlite这个坎后json不在话下了

map转json

// map数据转jsonfunc mapTojson() {data := map[string]string{"name": "zhangsan", "person": "something info"}jsonData, err := json.Marshal(data)if err != nil {fmt.Println("转json失败:", err)return}fmt.Println(string(jsonData))}

结构体转json

type person struct {Name string `json:name`Age  int    `json:age`}
// 结构体转jsonfunc structTojson() {user := person{Name: "lisi", Age: 18}jsonData, err := json.Marshal(user)if err != nil {fmt.Println("转json失败:", err)return}fmt.Println(string(jsonData))}

json写入文件

type person struct {Name string `json:name`Age  int    `json:age`}
// 将json数据写入文件func outputJson() {user := person{Name: "lisi", Age: 18}jsonData, err := json.MarshalIndent(user, "", "\t") //先格式化再写入,这里的缩进采用tabif err != nil {fmt.Println("转json失败:", err)return}file, err := os.OpenFile("output.json", os.O_CREATE|os.O_RDWR, 0666)if err != nil {fmt.Println("打开文件失败:", err)return}defer file.Close()_, err = file.Write(jsonData)if err != nil {fmt.Println("写入文件失败:", err)return}}

json编解码

type person struct {Name string `json:name`Age  int    `json:age`}
// json编解码func jsonEncoderDecoder() {user := person{Name: "wangwu",Age:  16,}file, err := os.OpenFile("test.json", os.O_CREATE|os.O_RDWR, 06666)if err != nil {fmt.Println("打开文件失败:", err)}defer file.Close()//编码jsonencoder := json.NewEncoder(file)encoder.SetIndent("", "\t") //格式添加taberr = encoder.Encode(user)if err != nil {fmt.Println("转json失败:", err)}//解码var newUser personfile, err = os.OpenFile("test.json", os.O_RDONLY, 0666)decoder := json.NewDecoder(file)err = decoder.Decode(newUser) //将加载的file文件json数据解析到newUser中if err != nil {fmt.Println("转换失败:", err)}fmt.Printf("Name: %s, Age: %d\n", user.Name, user.Age)}

json转结构体


type person2 struct {Name     string `json:name`Age      int    `json:age`Location struct {City  string `json:city`Other string `json:other`} `json:location`}
// 使用Unmarshal读取到的json文本数据解析到struct中func jsonTostruct_Unmarshal() {file, err := os.OpenFile("test2.json", os.O_CREATE|os.O_RDWR, 0666)if err != nil {fmt.Println("打开文件失败:", err)}res, err := ioutil.ReadAll(file)if err != nil {fmt.Println("读取失败:", err)}var user2 person2if err := json.Unmarshal(res, &user2); err != nil {fmt.Println("json转struct失败:", err)}fmt.Println("转换成功:", user2.Location.City) //验证一下即可//结构体格式化json(MarshalIndent)jsonData, err := json.MarshalIndent(user2, "", "\t") //记得给制表符if err != nil {fmt.Println("struct转json失败", err)}fmt.Println(string(jsonData)) //验证是否转换成功}

json转map

// json文本数据转mapfunc jsonTomap_Unmarshal() {file, err := os.OpenFile("test2.json", os.O_CREATE|os.O_RDWR, 0666)if err != nil {fmt.Println("打开文件失败:", err)}defer file.Close()data, err := io.ReadAll(file)if err != nil {fmt.Println("读取失败:", err)}var user2 map[string]interface{}//同理上一次转struct一样,这里是转为map而已if err := json.Unmarshal(data, &user2); err != nil {fmt.Println("转换失败:", err)}fmt.Println("验证是否转成功:", user2["Location"])//map格式化jsonres, err := json.MarshalIndent(user2, "", "\t")if err != nil {fmt.Println("转换失败:", err)}//验证是否转回来成功fmt.Println(string(res))}

json转string

// 直接从json文件转json字符串即可,// 不用其他什么自己写一个结构体啥的,// 如果贪图快就直接转字符串func jsonTostring() {file, err := os.OpenFile("test2.json", os.O_CREATE|os.O_RDWR, 0666)if err != nil {fmt.Println("打开文件失败:", err)}defer file.Close()res, err := io.ReadAll(file)if err != nil {fmt.Println("读取文件失败:", err)}var strjson interface{}if err := json.Unmarshal(res, &strjson); err != nil {fmt.Println("解析失败:", err)}fmt.Println("查看是否解析成功(还未格式化):", strjson) //这里还没格式化//接下来进行格式化res, err = json.MarshalIndent(strjson, "", "\t")if err != nil {fmt.Println("格式化失败:", err)}fmt.Println(string(res))}

练习:nmap扫描结果导出json格式

需求:
通过读取json配置文件,配置文件可以控制变量传到nmap扫描,扫描结果以json格式导出

{"ip_addresses": ["127.0.0.1", "192.168.1.1"],"port_range": "1-1024","timeout": 5}

细节分块:

  • nmapScan函数:使用nmap扫描return结果
    通过传参形式,将最大延迟时间和端口范围给到函数内部namp进行扫描

  • getConfig函数:这没啥好讲,我单独拿出来只是为了代码容易读一点,就是读取配置文件返回一个map类型数据

  • startScan函数:这里有一个之前没学过的知识点,断言,用于从 interface{} 类型的值中提取其具体类型,比如:value, ok := interfaceValue.(具体类型)
    这样就是强制的将你interface不指定的类型变量强制指定一个类型使用(非常好用)
    还有一个细节就是在json文件中读取出来的数字默认为float64,他直接给了最大的浮点数范围了,怕你不够用,所以我这里进行了类型转换

    最后一个细节就是:return的*nmap.NmapRun是一个切片,因为我们扫描的ip可能是多个的,不然就是只返回最后扫描的那个ip了。

  • scanResultOutputJson函数
    这里我是使用结构体,根据json输出的字段定义了一下

    接收的result也是nmap刚刚讲的扫描的多个结果,同时我用时间戳作为文件名前缀以防多次不同扫描结果冲突或者覆盖,其他没啥问题了就正常写入json文件即可。


先看运行截图,后面放源代码
(ps:两张截图之间没有联系)


示例代码:

package mainimport ("encoding/json""fmt""io""io/ioutil""log""os""os/exec""strconv""time""github.com/lair-framework/go-nmap")// 将json文件存储扫描目标,加载进来作为,进行nmap扫描结果输出output到json文件中// 使用nmap扫描return结果func nmapScan(target string, port_range string, timeout int) (*nmap.NmapRun, time.Time) {// 执行Nmap扫描// -sV:服务探测,-T4:扫描速度//--max-rtt-timeout控制每一个端口最大超时时间cmd := exec.Command("nmap", "-sV", "-T4", "--max-rtt-timeout", strconv.Itoa(timeout), "-p", port_range, "-oX", "-", target)output, err := cmd.CombinedOutput()if err != nil {log.Fatalf("Nmap扫描失败: %v\n输出: %s", err, string(output))}// 解析Nmap输出result, err := nmap.Parse(output)if err != nil {log.Fatalf("解析失败: %v", err)}// 打印结果for _, host := range result.Hosts {fmt.Printf("主机: %s\n", host.Addresses[0].Addr)for _, port := range host.Ports {fmt.Printf(" 端口 %d/%s: %s %s\n",port.PortId,port.Protocol,port.Service.Name,port.Service.Product)}}return result, time.Now()}// 拿到配置数据func getConfig(configFile string) map[string]interface{} {file, err := os.OpenFile(configFile, os.O_CREATE|os.O_RDONLY, 0666)if err != nil {fmt.Println("打开文件失败:", err)}defer file.Close()data, err := io.ReadAll(file)if err != nil {fmt.Println("读取配置文件失败:", err)}var res map[string]interface{}if err := json.Unmarshal(data, &res); err != nil {fmt.Println("转map失败:", err)}// fmt.Println(res["ip_addresses"])return res}func startScan() ([]*nmap.NmapRun, time.Time) {config := getConfig("config.json")var result []*nmap.NmapRunvar r *nmap.NmapRunvar scanTime time.Time//断言,直接强制给定类型if ipList, ok := config["ip_addresses"].([]interface{}); ok {for _, host := range ipList {timeoutFloat, _ := config["timeout"].(float64) //在json读取出来的是float64类型r, scanTime = nmapScan(host.(string), config["port_range"].(string), int(timeoutFloat))result = append(result, r)}} else {fmt.Println("读取失败,ip_addresses应该为列表类型")}return result, scanTime}func scanResultOutputJson(result []*nmap.NmapRun, scanTime time.Time) {type res_struct struct {Id            int    `json:id`Ip_address    string `json:ip_address`Port          int    `json:port`Vulnerability string `json:vulnerability`Severity      string `json:severity`Timestamp     string `json:timestamp`}var ress []res_struct        //存储数据,最终要写入json文件中for _, res := range result { //遍历所有扫描结果for _, host := range res.Hosts { //遍历扫描完成的结果数据for index, port := range host.Ports {ress = append(ress, res_struct{Id:            index + 1,Ip_address:    host.Addresses[0].Addr,Port:          port.PortId,Vulnerability: port.Protocol,Severity:      "high",Timestamp:     scanTime.Format("2006年01月02日"),})}}}outputdata, err := json.MarshalIndent(ress, "", "\t")if err != nil {fmt.Println("格式化失败:", err)}//输出文件名通过时间戳来表示就不会出错了fileName := fmt.Sprintf("%d_scan_result.json", time.Now().Unix())//意思是清空该文件的内容先,其实没啥用这里,用来当一个知识点吧file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)if err != nil {fmt.Println("打开文件失败:", err)}_, err = file.Write(outputdata)if err != nil {fmt.Println("导出json文件失败:", err)}}func main() {//程序运行scanResultOutputJson(startScan())
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/71651.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SwanLab简明教程:从萌新到高手

目录 1. 什么是SwanLab&#xff1f; 1.1 核心特性 2. 安装SwanLab 3. 登录SwanLab账号&#xff08;云端版&#xff09; 4. 5分钟快速上手 更多案例 5. SwanLab功能组件 5.1 图表视图 5.2 表格视图 5.3 硬件监控 5.4 环境记录 5.5 组织协同 6. 训练框架集成 6.1 基…

2025天梯训练1

PTA | L3-1 直捣黄龙 30分 思路&#xff1a;多关键字最短路&#xff0c;同时还要记录最短路径条数。 typedef struct node{int from,d,pass,kl;bool operator<(const node &x)const{if(d!x.d) return d>x.d;if(pass!x.pass) return pass<x.pass;return kl<x.…

EasyRTC嵌入式视频通话SDK的跨平台适配,构建web浏览器、Linux、ARM、安卓等终端的低延迟音视频通信

1、技术背景 WebRTC是一项开源项目&#xff0c;旨在通过简单的API为浏览器和移动应用程序提供实时通信&#xff08;RTC&#xff09;功能。它允许在无需安装插件或软件的情况下&#xff0c;实现点对点的音频、视频和数据传输。 WebRTC由三个核心组件构成&#xff1a; GetUserM…

【git】ssh配置提交 gitcode-ssh提交

【git】ssh配置提交 gitcode-ssh提交 之前一直用的是gitee和阿里云的仓库&#xff0c;前两天想在gitcode上面备份一下我的打洞代码和一些资料 就直接使用http克隆了下来 。 在提交的时候他一直会让我输入账号和密码&#xff0c;但是我之前根本没有设置过这个&#xff0c;根本没…

Dify部署踩坑指南(Windows+Mac)

组件说明 Dify踩坑及解决方案 ⚠️ 除了修改镜像版本&#xff0c;nginx端口不要直接修改docker-compose.yaml &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 1、更换镜像版本 这个文件是由.env自动生成的&#xff0c;在.env配置 …

Linux进程调度与管理:(五)进程的调度之调度节拍

《Linux6.5源码分析&#xff1a;进程管理与调度系列文章》 本系列文章将对进程管理与调度进行知识梳理与源码分析&#xff0c;重点放在linux源码分析上&#xff0c;并结合eBPF程序对内核中进程调度机制进行数据实时拿取与分析。 在进行正式介绍之前&#xff0c;有必要对文章引…

K8S学习之基础十七:k8s的蓝绿部署

蓝绿部署概述 ​ 蓝绿部署中&#xff0c;一共有两套系统&#xff0c;一套是正在提供服务的系统&#xff0c;一套是准备发布的系统。两套系统都是功能完善、正在运行的系统&#xff0c;只是版本和对外服务情况不同。 ​ 开发新版本&#xff0c;要用新版本替换线上的旧版本&…

【定制开发】碰一碰发视频系统定制开发,支持OEM

在短视频营销爆发的2025年&#xff0c;"碰一碰发视频"技术已成为实体商家引流标配。某连锁餐饮品牌通过定制化开发&#xff0c;单月视频发布量突破10万条&#xff0c;获客成本降低80%&#xff01;本文将深入解析该系统的技术架构与开发要点&#xff0c;助你快速搭建高…

[Lc7_分治-快排] 快速选择排序 | 数组中的第K个最大元素 | 库存管理 III

目录 1. 数组中的第K个最大元素 题解 代码 2.库存管理 III 代码 1. 数组中的第K个最大元素 题目链接&#xff1a;215. 数组中的第K个最大元素 题目分析&#xff1a; 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要…

AI视频生成工具清单(附网址与免费说明)

以下是一份详细的AI视频制作网站总结清单&#xff0c;包含免费/付费信息及核心功能说明&#xff1a; AI视频生成工具清单&#xff08;附网址与免费说明&#xff09; 1. Synthesia 网址&#xff1a;https://www.synthesia.io是否免费&#xff1a;免费试用&#xff08;生成视频…

dp_走方格(包含dfs分析,记忆化搜索)

类似题目解析&#xff1a;dp_最长上升子序列&#xff08;包含dfs分析&#xff0c;记忆化搜索&#xff09;-CSDN博客 题目链接&#xff1a;2067. 走方格 - AcWing题库 题目图片&#xff1a; 分析题目&#xff08;dfs&#xff09; 这个题目说有一个行为n行&#xff0c;列为m列…

Windows系统安装python2025最新安装包,包括环境配置,以及安装python编程软件PyCharm2024.3.3免费社区版本,详细全流程

一、python安装包安装 1、python安装包下载 浏览器打开官网&#xff0c;最好是谷歌浏览器 https://www.python.org/downloads/windows/ 下载安装包&#xff08;注意处理器是32位还是64位&#xff09; 注意&#xff1a;下载完成后&#xff0c;找到安装包并双击运行。在安装向导…

【GPT入门】第3课 客服会话质检(思维链)

【GPT入门】第3课 客服会话质检 1.质检任务2. 代码3.核心 1.质检任务 任务本质是检查客服与用户的对话是否有不合规的地方 质检是电信运营商和金融券商大规模使用的一项技术 每个涉及到服务合规的检查点称为一个质检项 我们选一个质检项&#xff0c;产品信息准确性&#xff0…

ubuntu 20.04 C++ 源码编译 cuda版本 opencv4.5.0

前提条件是安装好了cuda和cudnn 点击下载&#xff1a; opencv_contrib4.5.0 opencv 4.5.0 解压重命名后 进入opencv目录&#xff0c;创建build目录 “CUDA_ARCH_BIN ?” 这里要根据显卡查询一下,我的cuda是11&#xff0c;显卡1650&#xff0c;所以是7.5 查询方法1&#xff1…

K8s 1.27.1 实战系列(四)验证集群及应用部署测试

一、验证集群可用性 1、检查节点 kubectl get nodes ------------------------------------------------------ NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane 3h48m v1.27.1 k8s-node1 Ready <none> …

【C++设计模式】第七篇:桥接模式(Bridge)

注意&#xff1a;复现代码时&#xff0c;确保 VS2022 使用 C17/20 标准以支持现代特性。 抽象与实现的解耦之道 1. 模式定义与用途​​ 核心思想​ ​桥接模式&#xff1a;将抽象部分与实现部分分离&#xff0c;使二者可以独立变化。​关键用途&#xff1a; ​1.拆分复杂继承…

在 Spring Boot 2.7.x 中引入 Kafka-0.9 的实践

文章目录 在 Spring Boot 2.7.x 中引入 Kafka-0.9 的实践一、下载 Kafka-0.9二、启动 Zookeeper 和 Kafka三、创建 Spring Boot 项目四、引入 kafka 依赖五、移除 Kafka 自动配置六、编写 Kafka 生产者6.1 Kafka配置类6.2 生产者监听类 七、编写Controller发送Kafka八、验证消费…

字符串中的数字之和

题目描述 程序要求能够提取输入的字符串中的数字&#xff0c;将数字累加&#xff0c;得到数字之和&#xff0c;如输入的字符串为"abc76wet23er1.",应该提取数字76,23,1,求和后&#xff0c;即76231100。 输入格式: 输入一个字符串&#xff0c;字符串长度不超过100.…

77.ObservableCollection使用介绍1 C#例子 WPF例子

可观察集合ObservableCollection using System; using System.Collections.ObjectModel;class Program {static void Main(){// 创建一个可观察集合ObservableCollection<string> list new ObservableCollection<string>();// 注册集合变化事件list.CollectionCh…

ORACLE 执行查询语句慢(不走对应索引)

1. 索引未被创建或未正确创建 确保为查询中涉及的列创建了索引。例如&#xff0c;如果你经常需要按column_name列进行查询&#xff0c;确保已经为该列创建了索引,索引创建语句 CREATE INDEX idx_column_name ON table_name(column_name); 2、索引不可用 原因:索引可能被标记为不…