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

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格式 输出功能 在我们使用安全工具的…...

线性代数之矩阵特征值与特征向量的数值求解方法

文章目录 前言1. 幂迭代法&#xff08;Power Iteration&#xff09;幂法与反幂法求解矩阵特征值幂法求最大特征值编程实现补充说明 2. 逆幂迭代法&#xff08;Inverse Iteration&#xff09;移位反幂法 3. QR 算法&#xff08;QR Algorithm&#xff09;——稠密矩阵理论推导编程…...

Spring MVC源码分析のinit流程

文章目录 前言一、 init1.1、createWebApplicationContext1.2、onRefresh 二、请求处理器2.1、RequestMapping2.2、Controller接口2.3、HttpRequestHandler接口2.4、HandlerFunction 三、initHandlerMappings3.1、getDefaultStrategies3.1.1、RequestMappingHandlerMapping3.1.…...

【后端开发】go-zero微服务框架实践(goland框架对比,go-zero开发实践,文件上传问题优化等等)

【后端开发】go-zero微服务框架实践&#xff08;goland框架对比&#xff0c;go-zero开发实践&#xff0c;文件上传问题优化等&#xff09; 文章目录 1、go框架对比介绍2、go-zero 微服务开发实践3、go-zero 文件上传问题优化 1、go框架对比介绍 国内开源goland框架对比 1 go-…...

C#程序加密与解密Demo程序示例

目录 一、加密程序功能介绍 1、加密用途 2、功能 3、程序说明 4、加密过程 5、授权的注册文件保存方式 二、加密程序使用步骤 1、步骤一 ​编辑2、步骤二 3、步骤三 4、步骤四 三、核心代码说明 1、获取电脑CPU 信息 2、获取硬盘卷标号 3、机器码生成 3、 生成…...

小程序事件系统 —— 33 事件传参 - data-*自定义数据

事件传参&#xff1a;在触发事件时&#xff0c;将一些数据作为参数传递给事件处理函数的过程&#xff0c;就是事件传参&#xff1b; 在微信小程序中&#xff0c;我们经常会在组件上添加一些自定义数据&#xff0c;然后在事件处理函数中获取这些自定义数据&#xff0c;从而完成…...

深入解析 JavaScript 原型与原型链:从原理到应用

原型和原型链是 JavaScript 中实现对象继承和属性查找的核心机制。为了更深入地理解它们&#xff0c;我们需要从底层原理、实现机制以及实际应用等多个角度进行分析。 1. 原型&#xff08;Prototype&#xff09; 1.1 什么是原型&#xff1f; 每个 JavaScript 对象&#xff08…...

关于AI数据分析可行性的初步评估

一、结论&#xff1a;可在部分环节嵌入&#xff0c;无法直接处理大量数据 1.非本地部署的AI应用处理非机密文件没问题&#xff0c;内部文件要注意数据安全风险。 2.AI&#xff08;指高规格大模型&#xff09;十分适合探索性研究分析&#xff0c;对复杂报告无法全流程执行&…...

回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测

回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测 目录 回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测回归效果基本介绍GWO-BP-Adaboost:基于灰狼算法优化BP神经网络结合Adaboost思想…...

ARM Cortex-M 内存映射详解:如何基于寄存器直接读写 寄存器映射方式编码程序 直接操作硬件寄存器来控制 MCU

ARM Cortex-M 的系统映射空间 ​ 在 STM32 等 ARM Cortex-M 系列 MCU 中&#xff0c;内存地址空间按照 存储功能 进行了严格划分&#xff0c;包括 Flash&#xff08;程序存储&#xff09;、RAM&#xff08;数据存储&#xff09;、外设寄存器&#xff08;GPIO、UART、SPI 等&am…...

深度学习实战车辆目标跟踪与计数

本文采用YOLOv8作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对车辆目标数据集进行训练和优化&#xff0c;该数据集包含丰富的车辆目标图像样本…...

django中视图作用和视图功能 以及用法

在 Django REST Framework(DRF)中,视图(View)是处理 HTTP 请求并返回响应的核心组件。DRF 提供了多种视图类,适用于不同的场景和需求。以下是 DRF 中常见的视图类及其作用、使用方法的详细说明: 一、DRF 视图的分类 DRF 的视图可以分为以下几类: 基于函数的视图(Func…...

【每日学点HarmonyOS Next知识】输入框自动获取焦点、JS桥实现方式、Popup设置全屏蒙版、鼠标事件适配、Web跨域

1、HarmonyOS TextInput或TextArea如何自动获取焦点&#xff1f; 可以使用 focusControl.requestFocus 对需要获取焦点的组件设置焦点&#xff0c;具体可以参考文档&#xff1a; https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-universal-attribut…...

【学习思维模型】

学习思维模型 一、理解类模型二、记忆类模型三、解决问题类模型四、结构化学习模型五、效率与习惯类模型六、高阶思维模型七、实践建议八、新增学习思维模型**1. 波利亚问题解决四步法****2. 主动回忆(Active Recall)****3. 鱼骨图(因果图/Ishikawa Diagram)****4. MECE原则…...

MyBatis-Plus分页控件使用及使用过程发现的一个坑

最近维护一个旧项目的时候&#xff0c;出现了一个BUG&#xff0c;经排查后发现是Mybatis-plus分页控件使用的时候需要注意的一个问题&#xff0c;故在本地使用MybatisPlus模拟出现了一下这个问题。 首先&#xff0c;先说一下MyBatis-Plus的使用&#xff1a; 1&#xff09;引入…...

STM32的APB1和APB2的区别

STM32微控制器中的APB1和APB2的区别 STM32微控制器中的APB1和APB2是两种不同的外设总线&#xff0c;主要区别在于时钟速度、连接的外设以及用途。以下是它们的详细对比&#xff1a; 1. 时钟速度 APB1 (Advanced Peripheral Bus 1): 低速总线&#xff0c;时钟频率通常为系统时钟…...

JS一些小知识点

一、|| 运算符 plain this.ctx.body { type: type || 0, // ||在此处用法用于默认值填充&#xff0c;判断是否传参或该值是否存在&#xff0c;如果不存在就使用||后买你的值作为默认值 code: code || 0, msg: msg || SUCCESS, data: data || {}, ...others }; 二、trim() 方…...

手写Tomcat:实现基本功能

首先&#xff0c;Tomcat是一个软件&#xff0c;所有的项目都能在Tomcat上加载运行&#xff0c;Tomcat最核心的就是Servlet集合&#xff0c;本身就是HashMap。Tomcat需要支持Servlet&#xff0c;所以有servlet底层的资源&#xff1a;HttpServlet抽象类、HttpRequest和HttpRespon…...

C#变量与变量作用域详解

一、变量基础 1. ‌声明与初始化‌ 声明语法‌&#xff1a;<数据类型> <变量名>&#xff08;如 int age; string name&#xff09;‌初始化要求‌&#xff1a; 1、 类或结构体中的字段变量&#xff08;全局变量&#xff09;‌无需显式初始化‌&#xff0c;默认值…...

SV学习笔记——数组、队列

一、定宽数组 定宽数组是静态变量&#xff0c;编译时便已经确定其大小&#xff0c;其可以分为压缩定宽数组和非压缩定宽数组:压缩数组是定义在类型后面&#xff0c;名字前面;非压缩数组定义在名字后面。Bit [7:0][3:0] name; bit[7:0] name [3:0]; 1.1定宽数组声明 数组的声…...

API调试工具的无解困境:白名单、动态IP与平台设计问题

引言 你是否曾经在开发中遇到过这样的尴尬情形&#xff1a;你打开了平台的API调试工具&#xff0c;准备一番操作&#xff0c;结果却发现根本无法连接到平台&#xff1f;别急&#xff0c;问题出在调试工具本身。今天我们要吐槽的就是那些神奇的开放平台API调试工具&#xff0c;…...

Git清理本地残留的、但已经在服务器上被删除的分支

要筛选出已经被服务器删除的本地分支&#xff0c;并在本地删除这些分支&#xff0c;可以按照以下步骤进行操作&#xff1a; 步骤 1: 获取远程分支信息&#xff0c;确保本地的远程分支信息是最新的&#xff1a; git fetch -p步骤 2: 列出本地分支和远程分支&#xff1a; git …...

HTTPS实现内容加密的逻辑

加密过程 使用非对称加密&#xff0c;网站生成公钥和私钥浏览器获取到网站公钥&#xff08;通过验证和解析CA证书&#xff09;&#xff0c;随即生成一串字符串&#xff0c;然后使用公钥加密&#xff0c;发送给网站。网站用私钥将加密内容解析&#xff0c;然后使用这串字符串对…...

使用vue3.0+electron搭建桌面应用并打包exe

使用vue3.0electron搭建桌面应用并打包exe_如何使用electron将vue3vite开发完的项目打包成exe应用程序-CSDN博客...

JSAR 基础 1.2.1 基础概念_空间小程序

JSAR 基础 1.2.1 基础概念_空间小程序 空间空间自由度可嵌入空间空间小程序 最新的技术进展表明&#xff0c;官网之前的文档准备废除了&#xff0c;基于xsml的开发将退出历史舞台&#xff0c;three.js和普通web结合的技术将成为主导。所以后续学习请移步three.js学习路径&#…...

mysql练习

创建数据库db_ck&#xff0c;再创建表t_hero&#xff0c;将四大名著中的主要人物都插入这个表中&#xff0c;将实现过程中sql提交上上来 1、创建数据库db_ck mysql> create database db_ck; 2、创建表t_hero mysql> use db_ck Database changed mysql> create table …...

2025年2月平价旗舰手机性能对比

1、荣耀Magic7 点评&#xff1a;缺席潜望式长焦&#xff0c;3X直立长焦体验还行。兼顾性能、游戏、屏幕、影像、续航、快充等诸多方面&#xff0c;且外围配置比较齐全。 2、vivo x200 点评&#xff1a;潜望式长焦相机&#xff0c;拍照效果好&#xff0c;30W无线充电着实鸡肋&a…...

基于Spring Boot的扶贫助农系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

物联网中如何增加其可扩展性 协议 网络 设备 还包括软件层面上的

物联网(IoT)系统的可扩展性是指系统能够随着设备数量、数据流量和业务需求的增长而灵活扩展的能力。为了增加物联网的可扩展性,需要从协议、网络、设备和软件等多个层面进行优化和设计。以下是一些具体的策略和方法: 1. 协议层面的可扩展性 1.1 采用轻量级协议 轻量级协议…...

基于DeepSeek与搜索引擎构建智能搜索摘要工具

基于DeepSeek与搜索引擎构建智能搜索摘要工具 1. 项目概述 本项目通过整合DuckDuckGo搜索引擎与DeepSeek大语言模型,实现了一个智能搜索摘要生成工具。系统可自动执行以下流程: 输入查询语句进行全网搜索获取并解析搜索结果调用AI模型生成结构化摘要输出带来源标注的专业级…...