freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

子域名收集联动Yak漏扫插件
2022-09-21 13:41:45
所属地 四川省

上期讲了如何低成本写一个被动扫描脚本,这期写一个主动扫描脚本。Yakit的基础安全工具中有一个子域名收集插件十分方便,本篇文章对这个插件做点改造,让它与漏扫插件联动,输入目标就可以自动化扫描漏洞。

Yak基础知识

在开始之前了解一下需要用到的库函数


如图,子域名扫描需要用到subdomain库,其中核心就是subdomain.Scan函数,其余都是参数函数。subdomain.Scan的默认参数如下

参数名

默认值

dnsServer

114.114.114.114/8.8.8.8

eachQueryTimeout

3s

eachSearchTimeout

10s

mainDict

3164条数据的默认字典

maxDepth

5

recursive

true

recursiveDict

163条数据的默认字典(子域名的字典)

targetConsurrent

10

targetTimeout

300s

wildcardStop

false

workerConcurrent

50

大多数时候使用默认参数就足够了,所以做一次子域名扫描如下

res, err := subdomain.Scan("baidu.com")
if err != nil {
    yakit.Error("构建子域名扫描失败:%v", err)
    die(err)
}

subdomain.Scan的返回值类型是chan *subdomain.SubdomainResult

*subdomain.SubdomainResult的结构体声明等信息在官网文章里有详细介绍 → https://www.yaklang.io/docs/buildinlibs/lib_subdomain/

编写插件

现在就将子域名扫描与漏扫插件联动起来,先对目标做子域名扫描,等待扫描结束后,再找出重点的ip的c段,对其进行端口扫描和漏洞扫描,流程很简单,流程图如下。

子域名扫描

target = "baidu.com"
res, err := subdomain.Scan(target) // res的类型是chan *subdomain.SubdomainResult
if err != nil {
    yakit.Error("构建子域名扫描失败:%v", err)
    die(err)
}

res是一个channel,所以subdomain.Scan执行时不会阻塞,每生成一条执行结果都会写入到res中,遍历res就可以拿到执行结果。

端口扫描

本篇基于子域名收集插件做修改,插件中记录了所有被解析的ip和c段的出现次数,存储到变量ipCounter和cClassCollector中,所以我打算从中找出重点ip和重点c段做端口扫描,再做漏洞扫描。

// 通过判断ip和c段出现次数判断是否需要扫描
ipThreshold = 5
cClassCollectorThreshold = 10
ports = "21,22,443,445,80,8000-8004,3306,3389,5432,6379,8080-8084,7000-7005,9000-9002,8443,7443,9443,7080,8070"

ipForScan = []
// 添加所有超过阈值的c段
yakit.Info("统计需要扫描的ip/c段")
for c,count = range cClassCollector{
    if count > cClassCollectorThreshold{
        ipForScan = append(ipForScan,c)
    }
}
for ip,count = range ipCounter{
    if count > ipThreshold{
        network = str.IPv4ToCClassNetwork(ip)
        network = network[0]
        // 如果ip数量超过了阈值,且不在已添加的c段中,则将ip添加进扫描
        if cClassCollector[network] < cClassCollectorThreshold{
            ipForScan = append(ipForScan,ip)
        }
    } 
}

// 开始扫描
targets = str.Join(ipForScan,",")
res, err = servicescan.Scan(targets,ports,servicescan.probeTimeout(10))
if err != nil {
    yakit.Error("servicescan %v failed: %s", t)
    return
}

调用port-scan插件

调用方式和上一篇的大致相同,不过这次端口扫描插件是通过HandleServiceScanResult方法调用

// 初始化manager
yakit.Info("开始漏洞扫描")
manager, err = hook.NewMixPluginCaller()
// 重定向输出,和上一篇不同,这里使用yakit.Info向UI输出
manager.SetFeedback(func(i){
    msg = json.loads(i.Message)
    data = msg.content.data
    level = msg.content.level 
    switch msg.content.level{
    case "info":
        yakit.Info(data)
    case "error":
        yakit.Error(data)
    default:
        yakit.Info("收到信息,不支持的信息类型: [%s] %s",level,data)
    }
})
if err != nil {
    println("build mix plugin caller failed: %s", err)
    die(err)
}
yakScriptsChan = db.YieldYakScriptAll()
for yakscript = range yakScriptsChan{
    if yakscript.Type == "port-scan"{
        manager.LoadPlugin(yakscript.ScriptName)
    }
}
// 调用插件
for result = range res {
    yakit.Info("正在扫描目标: %s:%d", result.Target,result.Port)
    manager.HandleServiceScanResult(result)
}
manager.Wait()

优化信息展示

至此子域名扫描和漏洞扫描联动就完成了,但是还需要一些优化。如图,可以看见,子域名收集插件会将信息以表格的形式展现出来,所以需要一些优化,将端口扫描与漏洞扫描的结果也通过ui展示出来。

这里主要需要用到yakit.EnableTableyakit.TableData函数,yakit.EnableTable是启动一个页表格,需要两个参数,例如yakit.EnableTable("用户", ["姓名", "年龄"]),第一个参数是表名,相当于excel的工作簿名,第二个参数是字段名。yakit.TableData用于向表格输出数据,需要两个参数,例如yakit.TableData("用户", {"姓名":"张三","年龄":20}),第一个参数指定输出到的表,第二个参数是数据。

yakit.EnableTable("端口扫描信息表", ["主机地址", "HTML Title","指纹"]) // 创建一页表格
// outputTablePortScan方法用于向表格写入数据
outputTablePortScan = func(addr, title,fingerPrints) {
    data = make(map[string]var)
    data["主机地址"] = addr
    data["HTML Title"] = title
    data["指纹"] = fingerPrints
    yakit.TableData("端口扫描信息表", data)
}

端口扫描结果这里稍作修改就可以了

// 调用插件
for result = range res {
    outputTablePortScan(
        sprintf("%s:%d", result.Target,result.Port),
        result.GetHtmlTitle(),
        result.GetServiceName(),
    )
    yakit.Info("正在扫描目标: %s:%d", result.Target,result.Port)
    manager.HandleServiceScanResult(result)
}
manager.Wait()

UI优化

可能很多人没注意,yakit插件还支持UI联动,如图

联动后的效果就和"端口扫描/指纹"插件效果类似,所以代码还可以再优化一下。启动联动端口扫描插件后,将加载插件部分代码改成如下

scriptNameFile = cli.String("--yakit-plugin-file")
scriptNames = x.If(scriptNameFile != "", x.Filter(
    x.Map(
        str.Split(string(file.ReadFile(scriptNameFile)[0]), "|"), 
        func(e){return str.TrimSpace(e)},
    ), func(e){return e!=""}), make([]string))

x.Foreach(scriptNames, func(e){
    manager.LoadPlugin(e)
})

再将插件添加到菜单栏

测试脚本

运行脚本测试如图

最终脚本

yakit.AutoInitYakit()
// 通过判断ip和c段出现次数判断是否需要扫描
ipThreshold = cli.Int("ipThreshold")
cClassCollectorThreshold = cli.Int("cClassCollector")
target = cli.String("target", cli.setDefault("uestc.edu.cn"))
ports = cli.String("ports")

notRecursive = cli.Bool("not-recursive", cli.setHelp("设置是否递归爆破?"))
wildcardToStop = cli.Bool("wildcard-to-stop") // 泛解析停止

res, err := subdomain.Scan(target, subdomain.recursive(!notRecursive), subdomain.wildcardToStop(wildcardToStop))
if err != nil {
    yakit.Error("构建子域名扫描失败:%v", err)
    die(err)
}

/**
type palm/common/subdomain.(SubdomainResult) struct {
      FromTarget: string  
      FromDNSServer: string  
      FromModeRaw: int  
      IP: string  
      Domain: string  
      Tags: []string  
  StructMethods(结构方法/函数): 
  PtrStructMethods(指针结构方法/函数): 
      func Hash() return(string) 
      func Show() 
      func ToString() return(string) 
}
*/

yakit.Info("开始准备处理子域名收集的结果")
yakit.EnableTable("端口扫描信息表", ["主机地址", "HTML Title","指纹"])
yakit.EnableTable("涉及C段表", ["涉及C段"])
yakit.EnableTable("子域名表", ["Domain", "IP"])
outputTableCClass = func(network) {
    data = make(map[string]var)
    data["涉及C段"] = network
    yakit.TableData("涉及C段表", data)
}
outputTable = func(domain, ip) {
    data = make(map[string]var)
    data["Domain"] = domain
    data["IP"] = ip
    yakit.TableData("子域名表", data)
}

outputTablePortScan = func(addr, title,fingerPrints) {
    data = make(map[string]var)
    data["主机地址"] = addr
    data["HTML Title"] = title
    data["指纹"] = fingerPrints
    yakit.TableData("端口扫描信息表", data)
}

count = 0
savedCount = 0
mux = sync.NewLock()
statusAddCount = func() {
    mux.Lock()
    defer mux.Unlock()

    count++
    yakit.StatusCard("已保存/已收集", str.f("%v/%v", savedCount, count))
}
statusSavedAddCount = func() {
    mux.Lock()
    defer mux.Unlock()

    savedCount++
    yakit.StatusCard("已保存/已收集", str.f("%v/%v", savedCount, count))
}

swg = sync.NewSizedWaitGroup(40)
submitToDB = func(domain, ip) {
    swg.Add()
    go func{
        defer swg.Done()
        yakit.SaveDomain(domain, ip)
        statusSavedAddCount()
    }
}

ipCounter = make(map[string]int)
cClassCollector = make(map[string]int)
for result = range res {
    ip = result.IP
    if ipCounter[ip] == undefined {
        ipCounter[ip] = 0
    }
    ipCounter[ip] ++
    if ipCounter[ip] > 10 {
        yakit.StatusCard(sprintf("IP:%v 解析数", ip), ipCounter[result.IP])
    }

    result.Show()
    statusAddCount()
    outputTable(result.Domain, result.IP)
    network = str.IPv4ToCClassNetwork(result.IP)
    network = network[0]
    if cClassCollector[network] == undefined {
        cClassCollector[network] = 0
        outputTableCClass(network)
    }
    cClassCollector[network] ++
    // yakit.StatusCard(sprintf("C段:%v 主机数", network), cClassCollector[network])
    
    submitToDB(result.Domain, result.IP)
}

swg.Wait()

ipForScan = []
// 添加所有超过阈值的c段
yakit.Info("统计需要扫描的ip/c段")
for c,count = range cClassCollector{
    if count > cClassCollectorThreshold{
        ipForScan = append(ipForScan,c)
    }
}
for ip,count = range ipCounter{
    if count > ipThreshold{
        network = str.IPv4ToCClassNetwork(ip)
        network = network[0]
        // 如果ip数量超过了阈值,且不在已添加的c段中,则将ip添加进扫描
        if cClassCollector[network] < cClassCollectorThreshold{
            ipForScan = append(ipForScan,ip)
        }
    } 
}

// 开始扫描
targets = str.Join(ipForScan,",")
yakit.Info("开始端口扫描,target: %s,port: %s",targets,ports)
res, err = servicescan.Scan(targets,ports,servicescan.probeTimeout(10))
if err != nil {
    yakit.Error("servicescan %v failed: %s", t)
    return
}
// 初始化manager
yakit.Info("开始漏洞扫描")
manager, err = hook.NewMixPluginCaller()
if err != nil {
    println("build mix plugin caller failed: %s", err)
    die(err)
}
manager.SetFeedback(func(i){
    msg = json.loads(i.Message)
    data = msg.content.data
    level = msg.content.level 
    switch msg.content.level{
    case "info":
        yakit.Info(data)
    case "error":
        yakit.Error(data)
    default:
        yakit.Info("收到信息,不支持的信息类型: [%s] %s",level,data)
    }
})
scriptNameFile = cli.String("--yakit-plugin-file")
scriptNames = x.If(scriptNameFile != "", x.Filter(
    x.Map(
        str.Split(string(file.ReadFile(scriptNameFile)[0]), "|"), 
        func(e){return str.TrimSpace(e)},
    ), func(e){return e!=""}), make([]string))

x.Foreach(scriptNames, func(e){
    manager.LoadPlugin(e)
})
// 调用插件
for result = range res {
    outputTablePortScan(
        sprintf("%s:%d", result.Target,result.Port),
        result.GetHtmlTitle(),
        result.GetServiceName(),
    )
    yakit.Info("正在扫描目标: %s:%d", result.Target,result.Port)
    manager.HandleServiceScanResult(result)
}
manager.Wait()

总结

本篇文章通过对子域名扫描与端口扫描插件联动实现自动化对某域名目标的漏洞扫描,yakit的库函数不仅可以方便的调用插件,实现各种联动,还可以自动生成UI,表格的数据还可以一键导出,真的很方便,师傅们用起来!

# 网络安全 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录