- 输出功能
- CSV输出
- CSV 转 结构体
- 结构体 转 CSV
- 端口扫描结果使用CSV格式导出
- HTML输出
- Sqlite输出
- nmap扫描
- JSON
- map转json
- 结构体转json
- json写入文件
- json编解码
- json转结构体
- json转map
- json转string
- 练习:nmap扫描结果导出json格式
- CSV输出
输出功能
在我们使用安全工具的时候基本都会有一个输出功能,同样也很重要,所以下面介绍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.WaitGroup ch = make(chan int, 1024) // 增加缓冲区,减少阻塞 count int workers = 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 <- 1 go 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 string Ports string ScanTime 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.WaitGroup ch = make(chan int, 1024) // 增加缓冲区,减少阻塞 count int workers = 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 <- 1 go 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,即张三的年龄更改为45 updateSQL = `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") //先格式化再写入,这里的缩进采用tab if 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() //编码json encoder := json.NewEncoder(file) encoder.SetIndent("", "\t") //格式添加tab err = encoder.Encode(user) if err != nil { fmt.Println("转json失败:", err) } //解码 var newUser person file, 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 person2 if 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格式化json res, 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.NmapRun var r *nmap.NmapRun var 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())
}