GO工具开发|基于网站API的子域名与IP反查工具(一)

  • A+
所属分类:安全工具


GO工具开发|基于网站API的子域名与IP反查工具(一)

点击上方蓝字关注我们

0x00 前言

最近开始学习GO语言,希望可以摆脱脚本小子的苦恼,在需要的时候可以根据需要写一些小工具。在做信息搜集的时候无意间发现了一个网站,提供了API接口,于是产生了通过脚本来方便的获取数据的想法。脚本很简单也比较基础,需要的功能百度一下就可以找到实现方法,学习完基本语法就可以来写些小工具啦。由于只用到了一个网站,所以数据肯定是不全面的,后续有需要可以自行添加。

本文中用到的网站是dnsgrep.cn,提供了API查询接口,接口token需要邮箱申请,免费的。

0x01 实现

1、思路

既然是走的网站接口,那么首先需要发送一个请求,获取网站的响应并且解析数据,然后将解析的数据输出出来。首先分析一下网站响应数据的格式。

GO工具开发|基于网站API的子域名与IP反查工具(一)

可以看到,返回的数据时json格式,go语言提供了json.Unmarshal()函数来解析json数据,整理一下json格式,这里如果数据很长可以用在线的json格式美化网站

GET /api/query?q=[ip或者域名]&token=[申请的token值] HTTP/2Host: www.dnsgrep.cnCookie: UM_distinctid=17c89fa6562f0f-061b1aa8d7f106-6373267-1fa400-17c89fa6563847; CNZZDATA1279463756=1182095902-1634402460-%7C1634822243Sec-Ch-Ua: "Chromium";v="91", " Not;A Brand";v="99"Sec-Ch-Ua-Mobile: ?0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Sec-Fetch-Site: noneSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: documentAccept-Language: zh-CN,zh;q=0.9Accept-Encoding: gzip, deflateConnection: close
子域名格式:{ "status":200, "data":{ "data":[ {"domain":"wolai.com","value":"139.196.188.106","type":"A","time":"2021-06-26"}, {"domain":"wolai.com","value":"5 mxbiz1.qq.com","type":"MX","time":"2021-06-28"}, {"domain":"www.wolai.com","value":"www.wolai.com.w.kunlunca.com","type":"CNAME","time":"2021-06-27"} ], "count":16, "type":1 }}
ip反查格式:{ "status":200, "data":{ "data":[ {"domain":"47.98.84.224","value":"47.98.84.224","type":"A","time":"2021-06-25"}, {"domain":"a.zhongan.com","value":"47.98.84.224","type":"A","time":"2021-06-25"}, {"domain":"zhongan-xflow-nginx.zhongan.com","value":"47.98.84.224","type":"A","time":"2021-06-27"}, {"domain":"zhongan.com","value":"47.98.84.224","type":"A","time":"2021-06-27"} ], "count":24, "type":2 }}

子域名查询和ip反查的格式是一样的。首先最外层是status、data两个字段,status是int类型,data字段内容又包含了data、count和type字段,内层的data是一个字典的切片,count和type是int类型。下面来构造一下存放解析数据的结构体,这里可以使用结构体嵌套。

//外层的字段type JsonData struct {    Status    int       `json:"status"`    Data      Info      `json:"data"`}//内层字段type Info struct {    Data    []map[string]string  `json:"data"`    Count   int    Type    int}

这里建了一个vars的包,用于定义整个程序的全局变量

package vars
type JsonData struct { Status int `json:"status"` Data Info `json:"data"`}type Info struct { Data []map[string]string `json:"data"` Count int Type int}
var ( //声明一个全局变量V,JsonData类型,用于存放数据 V JsonData Target string)

2、函数部分

建一个util包,用于程序调用的函数部分,首先,是对网站接口的请求函数:

func Request(target string) []byte {    //start := time.Now()    //由于默认的GET方法使用的默认的client没有超时时间,可以自定义一个client来设置超时时间    client := &http.Client{Timeout: 5*time.Second}    url := "https://www.dnsgrep.cn/api/query?q="+target+"&token=[邮箱申请的token]"    resp,err := client.Get(url)        if err != nil {        fmt.Println("请求链接失败!",err)        return nil    }else if resp.Status != "200 OK" {                //判断状态码        fmt.Println("请求链接失败,状态码不为200!")        return nil    }    defer resp.Body.Close()        //读取响应的body    text,err := ioutil.ReadAll(resp.Body)    if err != nil {        fmt.Println("读取响应失败!",err)        return nil    }    //end := time.Since(start)    //fmt.Printf("请求数据用时:%s",end)    return text}

下面来写解析json数据的函数:

func GetInfo(jsonData []byte) {    if jsonData == nil {        fmt.Println("获取数据失败!")        return    }    //将json格式反序列化,存放到变量V中    err := json.Unmarshal(jsonData, &vars.V)    if err != nil {        fmt.Println("数据解析失败!", err)        return    }}

现在已经获得了网站数据的解析结果,接下来就要将结果显示出来,普通的打印的方式由于数据长度不一致导致每行信息长短不一,不能对齐,不便于查看,这里使用了github.com/liushuochen/gotable三方库来进行表格化输出:

func OutPut()  {    fmt.Printf("共检索到%d条数据n",len(vars.V.Data.Data))    //定义一个表格    tb,err := gotable.Create("domain","value","type","time")    if err != nil{        fmt.Println("创建表格失败!",err)        os.Exit(0)    }    //添加行,因为V.Data.Data是一个字典切片,可以直接用tb.AddRows()批量添加    tb.AddRows(vars.V.Data.Data)    //输出表格    tb.PrintTable()}

如果我们想将结果保存到excel中,可以使用三方库github.com/xuri/excelize/v2来实现:

func WriteExcel()  {    f := excelize.NewFile()
// 设置表头 f.SetCellValue("Sheet1", "A1", "Domain") f.SetCellValue("Sheet1", "B1", "Value") f.SetCellValue("Sheet1", "C1", "Type") f.SetCellValue("Sheet1", "D1", "Time") //遍历数据,写到对应的列中 for i,r := range vars.V.Data.Data{ num := strconv.Itoa(i+2) //num用来控制写到哪行,第一个数据i=0,应该写到第二行 f.SetCellValue("Sheet1", "A"+num, r["domain"]) f.SetCellValue("Sheet1", "B"+num, r["value"]) f.SetCellValue("Sheet1", "C"+num, r["type"]) f.SetCellValue("Sheet1", "D"+num, r["time"]) } // 根据指定路径保存文件 if err := f.SaveAs("Result.xlsx"); err != nil { println(err.Error()) }}

3、主函数部分

上面通过不同函数实现了所需要的功能,接下来就要在主函数中调用:

对于需要输入参数部分,可以使用三方库github.com/urfave/cli来实现

package main
import ( "dnsgrep/util" "dnsgrep/vars" "fmt" "github.com/urfave/cli" "os" "time")
func main() { start := time.Now() //设置参数部分 app := &cli.App{ Name: "脚本名称", Author: "作者", Version: "2021-11-01", Usage: "简介", Flags: []cli.Flag{ //设定参数的标志 &cli.StringFlag{ Name: "t", //参数名称 Value: "", //设置参数的默认值 Usage: "target,域名或ip", //参数介绍,将显示在-h中 Destination: &vars.Target, //将输入的参数赋值给vars包中定义的全局变量Target }, }, Action: func(c *cli.Context) { //检查是否输入了目标参数,没输入直接退出 if c.IsSet("t") == false{ fmt.Println("请输入查询目标,-h查看参数!") os.Exit(0) } }, } app.Run(os.Args) if vars.Target == "" { os.Exit(0) } //发送请求并将返回值用作GetInfo()函数的输入 util.GetInfo(util.Request(vars.Target)) util.OutPut() util.WriteExcel() //统计程序运行时间 end := time.Since(start) fmt.Printf("共用时:%s",end)}

4、util包完整代码

package util
import ( "dnsgrep/vars" "encoding/json" "fmt" "github.com/liushuochen/gotable" "github.com/xuri/excelize/v2" "io/ioutil" "net/http" "os" "strconv" "time")
func Request(target string) []byte { start := time.Now() client := &http.Client{Timeout: 5*time.Second} url := "https://www.dnsgrep.cn/api/query?q="+target+"&token=[申请的token值]" resp,err := client.Get(url) if err != nil { fmt.Println("请求链接失败!",err) return nil }else if resp.Status != "200 OK" { fmt.Println("请求链接失败,状态码不为200!") return nil } defer resp.Body.Close()
text,err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("读取响应失败!",err) return nil } end := time.Since(start) fmt.Printf("请求数据用时:%s",end) return text}
func GetInfo(jsonData []byte) { if jsonData == nil { fmt.Println("获取数据失败!") return }
err := json.Unmarshal(jsonData, &vars.V) if err != nil { fmt.Println("数据解析失败!", err) return }}
func OutPut() { fmt.Printf("共检索到%d条数据n",len(vars.V.Data.Data)) tb,err := gotable.Create("domain","value","type","time") if err != nil{ fmt.Println("创建表格失败!",err) os.Exit(0) } tb.AddRows(vars.V.Data.Data) tb.PrintTable()}
func WriteExcel() { f := excelize.NewFile()
f.SetCellValue("Sheet1", "A1", "Domain") f.SetCellValue("Sheet1", "B1", "Value") f.SetCellValue("Sheet1", "C1", "Type") f.SetCellValue("Sheet1", "D1", "Time")
for i,r := range vars.V.Data.Data{ num := strconv.Itoa(i+2) f.SetCellValue("Sheet1", "A"+num, r["domain"]) f.SetCellValue("Sheet1", "B"+num, r["value"]) f.SetCellValue("Sheet1", "C"+num, r["type"]) f.SetCellValue("Sheet1", "D"+num, r["time"]) }
if err := f.SaveAs("Result.xlsx"); err != nil { println(err.Error()) }}

0x03 运行结果

运行一下,看看是不是我们想要的结果

GO工具开发|基于网站API的子域名与IP反查工具(一)

GO工具开发|基于网站API的子域名与IP反查工具(一)

0x04 总结

一个简单的单任务单线程的脚本就写好了。由于查询网站的单一,数据肯定是不全的,那么根据需要,可以自行扩展。下一篇会将这个脚本完善一下,改成多任务的多线程的查询,并简单优化一下使用体验。项目地址:https://github.com/MoYang233/subdomain-demo/tree/main/subdomain-demo1


GO工具开发|基于网站API的子域名与IP反查工具(一)



原文始发于微信公众号(灼剑安全团队):GO工具开发|基于网站API的子域名与IP反查工具(一)

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: