当前位置: 首页 > article >正文

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())
}

相关文章:

Go红队开发—格式导出

文章目录 输出功能CSV输出CSV 转 结构体结构体 转 CSV端口扫描结果使用CSV格式导出 HTML输出Sqlite输出nmap扫描 JSONmap转json结构体转jsonjson写入文件json编解码json转结构体json转mapjson转string练习&#xff1a;nmap扫描结果导出json格式 输出功能 在我们使用安全工具的…...

从零构建高可用MySQL自动化配置系统:核心技术、工具开发与企业级最佳实践

在现代企业级数据库管理中,手动配置 MySQL 已无法满足高效、稳定和可扩展的需求。本文从 MySQL 配置管理的核心原理 出发,深入剖析 自动化配置工具的架构设计、关键技术实现,并结合 企业级落地方案,帮助读者构建一套 高可用、智能化的 MySQL 自动化配置系统。无论是 DevOps…...

element-plus中table组件的使用

1、table组件的基本使用 注意&#xff1a; ①对象集合&#xff0c;要从后端查询。 ②prop是集合中的对象的属性名&#xff1b;label是表格表头的名称。 2、将性别一列的71转为男&#xff0c;72转为女 问题描述&#xff1a; 解决步骤&#xff1a; ①将el-table-column变成双标签…...

K8s 1.27.1 实战系列(三)安装网络插件

Kubernetes 的网络插件常见的有 Flannel 和 Calico ,这是两种主流的 CNI(容器网络接口)解决方案,它们在设计理念、实现方式、性能特征及适用场景上有显著差异。以下是两者的综合对比分析: 一、Flannel 和 Calico 1. 技术基础与网络实现 Flannel 核心机制:基于 Overlay …...

Java基础回顾 Day4

多线程相关 runnable接口实现&#xff0c;解决单继承的问题&#xff0c;因为继承Thread类就不能继承其他类了 Callable接口的特点是满足线程需要返回值和抛出异常的情况 在创建线程后的任何时候都可以重新设置&#xff0c;线程已经创建&#xff0c;可以使用 Thread.setPrior…...

Go加spy++隐藏窗口

最近发现有些软件的窗口就像狗皮膏药一样&#xff0c;关也关不掉&#xff0c;一点就要登录&#xff0c;属实是有点不爽了。 窗口的进程不能杀死&#xff0c;但是窗口我不想要。思路很简单&#xff0c;用 spy 找到要隐藏的窗口的句柄&#xff0c;然后调用 Windows 的 ShowWindo…...

Windows CMD 命令大全(综合开发整理版)

CMD Windows CMD 命令大全(综合整理版)基础操作与文件管理类系统维护与配置类网络与连接类开发者常用命令CMD 黑窗口使用技巧1. **效率操作**2. **高级功能**3. **开发者高效技巧**注意事项**微软官方文档****其他实用资源****如何高效使用官方文档**Windows CMD 命令大全(综…...

网络安全通信架构图

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在安全通信里面我经常听到的2个东西就是SSL和TLS&#xff0c;这2个有什么区别呢&#xff1f;以及HTTPS是怎么通信的&#xff1f;包括对称加密、非对称加密、摘要、…...

当中国“智算心跳”与全球共振:九章云极DataCanvas首秀MWC 2025

3月3日&#xff0c;西班牙巴塞罗那&#xff0c;全球通信与科技领域的盛会“2025世界移动通信大会&#xff08;MWC 2025&#xff09;”正式拉开帷幕。中国人工智能基础设施领军企业九章云极DataCanvas公司以全球化战略视野与硬核技术实力&#xff0c;全方位、多维度地展示了在智…...

Clion快捷键、修改字体

文章目录 一、Clion快捷键1.撤销&#xff1a;crtl Z2.重做&#xff1a;crtl shift Z3.删除该行&#xff1a;crtl Y4.多行后退&#xff1a;选中多行 Tab5.多行缩进&#xff1a;选中多行 shift Tab 二、修改注释的斜体 一、Clion快捷键 1.撤销&#xff1a;crtl Z 2.重做…...

基于PySide6的CATIA零件自动化着色工具开发实践

引言 在汽车及航空制造领域&#xff0c;CATIA作为核心的CAD设计软件&#xff0c;其二次开发能力对提升设计效率具有重要意义。本文介绍一种基于Python的CATIA零件着色工具开发方案&#xff0c;通过PySide6实现GUI交互&#xff0c;结合COM接口操作实现零件着色自动化。该方案成…...

在Uniapp中实现特殊字符弹出框并插入输入框

在开发Uniapp项目时&#xff0c;我们经常会遇到需要用户输入特殊字符的场景。为了提升用户体验&#xff0c;我们可以封装一个特殊字符弹出框&#xff0c;用户点击键盘图标后弹出该字符集&#xff0c;选择字符后自动插入到输入框中。本文将详细介绍如何实现这一功能。 1. 功能概…...

golang dlv调试工具

golang dlv调试工具 在goland2022.2版本 中调试go程序报错 WARNING: undefined behavior - version of Delve is too old for Go version 1.20.7 (maximum supported version 1.19) 即使你go install了新的dlv也无济于事 分析得出Goland实际使用的是 Goland安装目录下dlv 例…...

深入解析 BitBake 日志机制:任务调度、日志记录与调试方法

1. 引言&#xff1a;为什么 BitBake 的日志机制至关重要&#xff1f; BitBake 是 Yocto 项目的核心构建工具&#xff0c;用于解析配方、管理任务依赖&#xff0c;并执行编译和打包任务。在 BitBake 构建过程中&#xff0c;日志记录机制不仅用于跟踪任务执行情况&#xff0c;还…...

数据结构链表的C++实现

在C中实现链表是一种常见的练习&#xff0c;有助于理解指针和动态内存分配的概念。下面是一个简单的单向链表&#xff08;Singly Linked List&#xff09;的实现示例&#xff0c;包括基本的操作如插入、删除和遍历。 单向链表 (Singly Linked List) 实现 1. 定义节点结构 首…...

《原型链的故事:JavaScript 对象模型的秘密》

原型链&#xff08;Prototype Chain&#xff09; 是 JavaScript 中实现继承的核心机制。每个对象都有一个内部属性 [[Prototype]]&#xff08;可以通过 __proto__ 访问&#xff09;&#xff0c;指向其原型对象。每个对象都有一个原型&#xff0c; 原型本身也是一个对象&#xf…...

Linux 配置静态 IP

一、简介 在 Linux CentOS 系统中默认动态分配 IP 地址&#xff0c;每次启动虚拟机服务都是不一样的 IP&#xff0c;因此要配置静态 IP 地址避免每次都发生变化&#xff0c;下面将介绍配置静态 IP 的详细步骤。 首先先理解一下动态 IP 和静态 IP 的概念&#xff1a; 动态 IP…...

【Python 数据结构 10.二叉树】

目录 一、二叉树的基本概念 1.二叉树的定义 2.二叉树的特点 3.特殊的二叉树 Ⅰ、斜树 Ⅱ、满二叉树 Ⅲ、完全二叉树 Ⅳ、完全二叉树和满二叉树的区别 4.二叉树的性质 5.二叉树的顺序存储 Ⅰ、完全二叉树 Ⅱ、非完全二叉树 Ⅲ、稀疏二叉树 6.二叉树的链式存储 7.二叉树的遍历概念…...

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 基…...

SQLiteStudio:一款免费跨平台的SQLite管理工具

SQLiteStudio 是一款专门用于管理和操作 SQLite 数据库的免费工具。它提供直观的图形化界面&#xff0c;简化了数据库的创建、编辑、查询和维护&#xff0c;适合数据库开发者和数据分析师使用。 功能特性 SQLiteStudio 提供的主要功能包括&#xff1a; 免费开源&#xff0c;可…...

【智能体Agent】ReAct智能体的实现思路和关键技术

基于ReAct&#xff08;Reasoning Acting&#xff09;框架的自主智能体 import re from typing import List, Tuplefrom langchain_community.chat_message_histories.in_memory import ChatMessageHistory from langchain_core.language_models.chat_models import BaseChatM…...

python爬虫系列课程8:js浏览器window对象属性

python爬虫系列课程8:js浏览器window对象属性 一、JavaScript的组成二、document常见属性对象三、navigator对象一、JavaScript的组成 JavaScript可以分为三个部分:ECMAScript标准、DOM、BOM。 ECMAScript标准:即JS的基本语法,JavaScript的核心,描述了语言的基本语法和数…...

Java基础系列:深入理解八大基本数据类型及避坑指南

目录 一、基本数据类型概述 八大类型速查表 二、各类型详解与常见陷阱 1. 整型家族&#xff08;byte/short/int/long&#xff09; 2. 浮点型&#xff08;float/double&#xff09; 3. 字符型&#xff08;char&#xff09; 4. 布尔型&#xff08;boolean&#xff09; 三…...

贝塞尔曲线学习

1、一阶贝塞尔曲线 一阶贝塞尔曲线其实是一条直线——给定点 P0、P1&#xff0c;线性贝塞尔曲线就是一条两点之间的直线&#xff0c;公式如下&#xff1a; 一阶曲线很好理解, 就是根据t来线性插值。 void MainWindow::mousePressEvent(QMouseEvent *e) {list.append(e->pos…...

机器学习(六)

一&#xff0c;决策树&#xff1a; 简介&#xff1a; 决策树是一种通过构建类似树状的结构&#xff08;颠倒的树&#xff09;&#xff0c;从根节点开始逐步对数据进行划分&#xff0c;最终在叶子节点做出预测结果的模型。 结构组成&#xff1a; 根节点&#xff1a;初始的数据集…...

kotlin高级用法总结

Kotlin 是一门功能强大且灵活的编程语言&#xff0c;除了基础语法外&#xff0c;它还提供了许多高级特性&#xff0c;可以帮助你编写更简洁、高效和可维护的代码。以下是 Kotlin 的一些高级用法&#xff0c;涵盖了协程、扩展函数、属性委托、内联类、反射等内容。 协程&#x…...

OCPP扩展机制与自定义功能开发:协议灵活性设计与实践 - 慧知开源充电桩平台

OCPP扩展机制与自定义功能开发&#xff1a;协议灵活性设计与实践 引言 OCPP作为开放协议&#xff0c;其核心价值在于平衡标准化与可扩展性。面对不同充电桩厂商的硬件差异、区域能源政策及定制化业务需求&#xff0c;OCPP通过**扩展点&#xff08;Extension Points&#xff09…...

docker目录挂载与卷映射的区别

在 Docker 中&#xff0c;目录挂载&#xff08;Bind Mount&#xff09;和卷映射&#xff08;Volume Mount&#xff09;的命令语法差异主要体现在路径格式上&#xff0c;具体表现为是否以斜杠&#xff08;/&#xff09;开头。以下是两者的核心区别及使用场景的总结&#xff1a; …...

【江协科技STM32】ADC数模转换器-学习笔记

ADC简介 ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁&#xff0c;ADC是一种将连续的模拟信号转换为离散的数字信号的设备或模块12位逐次逼近型…...

【kubernetes】service

目录 1. 说明2. 原理2.1 服务注册2.2 服务发现2.3 负载均衡 3. Service的类型3.1 ClusterIP3.2 NodePort3.3 LoadBalancer3.4 ExternalName 4. 使用场景 1. 说明 1.kubernetes中的service主要用于提供网络服务&#xff0c;并实现微服务架构中的几个核心功能&#xff1a;全自动…...