Skip to main content

实战1: 扫端口/子域名+指纹识别

本届我们将学习的知识量非常大,这可能是很多开源项目的核心功能,我们要做的就是要把这些功能的实现成本降低。

之前我们要完成这些功能,可能需要一个语言的很多的库,然而在 Yak 中,这些全部被封装在了一个个的内置模块中。

信息收集流程设计#

流程图

这个流程虽然也并没有什么特殊的,也并没有任何特色,甚至大家可以认为这是一件很简单的事情,但是实际上,市面上想要找到一个这么简单的流程,也并不是一件容易的事情。

很多优秀的工具,只实现了其中的一部分,而且和其他工具的配合/联动,并不是特别的舒服。

于是市面上出现了一大批缝合项目,但是缝合项目也是有代价的,我们需要安装依赖,或者把整个项目运行在 docker 中。由于很多用户缺乏 docker 使用经验,也懒得去安装语言以及对应依赖 导致这些项目运行的效果并不够理想。

然而在 yak 中,上述问题都得到了一定程度上的解决。

0x01 处理用户输入#

内部依赖模块#

我们内部依赖的模块并不需要用户去导入,直接使用即可!

  1. cli:可以处理用户从命令行输入的参数。
  2. str:常见处理字符串的模块,用于解析扫描目标,判断字符串内容类型等操作。

Show Me the Code!#

我们在处理用户输入的时候,往往需要使用 cli 这个专门用来处理用户输入的包

文档在这里 cli 官方手册

我们根据文档编写了如下代码,

yakmisc.yak
targets := cli.String("target", cli.setHelp("扫描目标"))scanTargets := str.ParseStringToHosts(targets)if len(scanTargets) <= 0 {    die("empty target is not allowed")}
log.setLevel("info")for _, target := range scanTargets {    if str.IsIPv4(target) {        log.info("start to handle target ip: %v", target)    } else {        log.info("start to handle domain: %v", target)    }}

当我们编写上述小代码,可以使用

yak yakmisc.yak --target 192.168.1.23/29,example.com

获取到的结果为

[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.16[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.17[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.18[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.19[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.20[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.21[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.22[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle target ip: 192.168.1.23[INFO] 2021-07-01 11:57:28 +0800 [yakmisc] start to handle domain: example.com

我们观察结果:发现我们的代码已经可以把域名和 IP 分开了,从而进行分别处理

0x02 进行端口扫描#

内部依赖模块#

  1. cli 接收输入的端口参数
  2. servicescan 服务指纹扫描

Show Me the Code!#

当我们可以顺利接受目标之后,扫描端口其实是一个非常简单的事情,在我们之前的小课程中,我们已经学会了如何使用 servicescansynscan 了。

与此同时,我们也学习过如何编写 YAK 的并发程序。

所以我们编写这个部分其实是非常简单,我们以只使用 servicescan 为例:

log.setLevel("info")
targets := cli.String("target", cli.setHelp("扫描目标"))scanTargets := str.ParseStringToHosts(targets)if len(scanTargets) <= 0 {    die("empty target is not allowed")}
ports = cli.String("ports", cli.setDefault("80,8080-8082,443"))scanPortWg := sync.NewSizedWaitGroup(10)
// 处理指纹扫描的结果handleTarget = func(result) {    log.debug("found result: %v", result.String())    if result.IsOpen() {        log.info("scanned fingerprint %v", result.String())    }}
// 处理扫描 IP 的任务,使用 scanPortWg 进行并发控制func scanIPTargets(targetIP) {    scanPortWg.Add()
    go func(){        defer scanPortWg.Done()
        res, err := servicescan.Scan(targetIP, ports)        if err != nil {            log.error("servicescan %v ports:%v failed: %s", targetIP, ports, err)            return        }
        for result := range res {            handleTarget(result)        }    }()
}
for _, target := range scanTargets {    if str.IsIPv4(target) {        log.info("start to handle target ip: %v", target)        scanIPTargets(target)    } else {        log.info("start to handle domain: %v", target)    }}
wg := sync.NewWaitGroup()wg.Add(1)go func {    defer wg.Done()    scanPortWg.Wait()}
wg.Wait()

我们实现上述代码之后,其实可以实现一条线路的扫描,可以实现扫描目标中的网段和IP等。但是离我们自动进行域名收集仍然还差一点,我们将在下一节主要讲解如何使用 subdomain 模块

0x03 进行子域名收集#

我们完成之前的代码可以针对 IP 进行扫描了,但是这还不够,我们希望可以针对域名进行子域名收集相关的工作:

内部依赖模块#

  1. subdomain的使用教程
  2. subdomain所有可用API与参数配置手册

Show Me the Code!#

在同学们阅读了上述两个文档的之后,我们相信很容易将会得到下面的代码,简单结合了我们的并发限制的功能之后,我们得到了如下带总体并发限制的子域名检测:

......
scanDomainWg := sync.NewSizedWaitGroup(10)scanDomain = func(domain) {    scanDomainWg.Add()    go func{        defer scanDomainWg.Done()        defer fn{            err = recover()            if err != nil {                log.error("panic from scan Domain", err)            }        }        log.info("start to find subdomain for: %v", domain)        res, err := subdomain.Scan(domain, subdomain.recursive(true))        if err != nil {            log.error("subdomain scan[%s] failed: %s", domain, err)            return        }        for result := range res {            result.Show()        }    }}
......

我们编写完核心工作函数之后,把上述代码塞进之前的代码中。然后执行一下命令行扫描一下一些互联网资产

yak yakmisc.yak --target 47.5********05/24,b********.com --ports 80

在整合完之后

在这个代码中,我们仅仅把结果打印出来,在控制台我们除了之前的扫描端口的结果之外,我们还一直得到一些其他结果

    b*******der-111-*******-20.craw*******.com IP:[ 111.20*******0] From:[https://api.sublist3r.com/search.php?domain=**********.com]       bai*******r-1*******71-98.craw*******.com IP:[  12*******1.98] From:[https://api.sublist3r.com/search.php?domain=**********.com]                                  in*******u.com IP:[111.*******.224] From:[]                          lyncdiscover*******com IP:[ 111*******5.71] From:[]                                port*******u.com IP:[    1*******.82] From:[]                          river.zhid*******u.com IP:[ 220.*******133] From:[https://crt.sh/?q=%25.**********.com]                                ima*******du.com IP:[   18*******.50] From:[]                               finfe*******u.com IP:[  120*******131] From:[https://crt.sh/?q=%25.**********.com][INFO] 2021-07-01 14:24:07 +0800 [yakmisc] scanned fingerprint tcp://*******00.22:80     open   nginx[*]/php[*][INFO] 2021-07-01 14:24:07 +0800 [yakmisc] start to handle target ip:*******100.37[INFO] 2021-07-01 14:24:08 +0800 [yakmisc] scanned fingerprint tcp://47.*******21:80     open   gitlab[*]/nginx[*][INFO] 2021-07-01 14:24:08 +0800 [yakmisc] start to handle target ip: 47*******.38                                   n*******u.com IP:[  112*******.64] From:[]      b*******der-111-*******-76.cra*******u.com IP:[ 111*******8.76] From:[https://api.sublist3r.com/search.php?domain=**********.com]                                sta*******du.com IP:[111.*******.240] From:[]      b*******der-116-*******172.cr*******du.com IP:[ 116*******.172] From:[https://api.sublist3r.com/search.php?domain=**********.com]                                  h*******du.com IP:[ 11*******09.69] From:[]                              downl*******du.com IP:[ 111.*******.32] From:[]                                   *******du.com IP:[   1*******3.10] From:[]                                   *******du.com IP:[124.2*******254] From:[][INFO] 2021-07-01 14:24:11 +0800 [yakmisc] start to handle target ip: 4*******0.39[INFO] 2021-07-01 14:24:11 +0800 [yakmisc] start to handle target ip: 4*******0.40[INFO] 2021-07-01 14:24:11 +0800 [yakmisc] scanned fingerprint tcp://47.*******:80     open   apache[*]/http_server[*][INFO] 2021-07-01 14:24:11 +0800 [yakmisc] start to handle target ip: 47********.41[INFO] 2021-07-01 14:24:11 +0800 [yakmisc] scanned fingerprint tcp://47*******.35:80     open   iis[7.5]/int

0x04 进行子域名收集的结果交给端口扫描#

当我们完成上述扫描的时候,其实心里已经有数了,IP和域名我们都使用了不同的扫描策略,但是实际上,域名的扫描并没有达到我们想要的效果,我们想把域名扫描产生的结果直接交给端口扫描去扫描指纹。

虽然现在我们对域名扫描的结果使用非常简单仅仅是

res, err := subdomain.Scan(domain, subdomain.recursive(true))if err != nil {    log.error("subdomain scan[%s] failed: %s", domain, err)    return}for result := range res {    result.Show()}

我们把域名收集的结果打印出来到屏幕上了,然而这并没有满足我们的需求。其实只需要把 result 中的 IP 地址取出来,重新交给端口扫描/指纹扫描即可

res, err := subdomain.Scan(domain, subdomain.recursive(true))if err != nil {    log.error("subdomain scan[%s] failed: %s", domain, err)    return}for result := range res {    result.Show()    scanIPTargets(result.IP)}
info

如果用户不熟悉 subdomain 中有啥字段可以使用,可以参考文档 subdomain 中的结果处理

type palm/common/subdomain.(SubdomainResult) struct {  Fields(可用字段):      // 原来的域名是谁?      FromTarget: string
      // 来源于哪个 DNS 服务器的相应?      FromDNSServer: string
      FromModeRaw: int // 略
      // 这个域名对应的 IP 是多少      IP: string
      // 域名本身的信息      Domain: string
      // 额外信息      Tags: []string  StructMethods(结构方法/函数):  PtrStructMethods(指针结构方法/函数):      func Hash() return(string)
      // 展示到 Stdout      func Show()
      // 输出成字符串格式      func ToString() return(string)}

所以,我们只需要增加一行 scanIPTargets(result.IP),就可以把域名扫描的结果直接输入端口扫描结果进行扫描了

Show Me the Code!#

当我们把上面所有的内容和流程理解了之后,我们修改了我们的代码,下面是一个完整的 yakmisc.yak 脚本

大家可以执行以下命令:

yak yakmisc.yak --target 192.168.1.1/24,example.com,10.3.1.0/24
caution

当然,你需要记得把扫描目标换成真正你想要的扫描目标

yakmisc.yak
log.setLevel("info")
targets := cli.String("target", cli.setHelp("扫描目标"))scanTargets := str.ParseStringToHosts(targets)if len(scanTargets) <= 0 {    die("empty target is not allowed")}
ports = cli.String("ports", cli.setDefault("80,8080-8082,443"))scanPortWg := sync.NewSizedWaitGroup(10)
// 处理指纹扫描的结果handleTarget = func(result) {    log.debug("found result: %v", result.String())    if result.IsOpen() {        log.info("scanned fingerprint %v", result.String())    }}
scanIPTargets = func(targetIP) {    scanPortWg.Add()
    go func(){        defer scanPortWg.Done()
        log.info("start to handle target ip: %v", targetIP)        res, err := servicescan.Scan(targetIP, ports)        if err != nil {            log.error("servicescan %v ports:%v failed: %s", targetIP, ports, err)            return        }
        for result := range res {            handleTarget(result)        }    }()
}
scanDomainWg := sync.NewSizedWaitGroup(10)scanDomain = func(domain) {    scanDomainWg.Add()    go func{        defer scanDomainWg.Done()        defer fn{            err = recover()            if err != nil {                log.error("panic from scan Domain", err)            }        }        log.info("start to find subdomain for: %v", domain)        res, err := subdomain.Scan(domain, subdomain.recursive(true))        if err != nil {            log.error("subdomain scan[%s] failed: %s", domain, err)            return        }        for result := range res {            result.Show()            scanIPTargets(result.IP)        }    }}
for _, target := range scanTargets {    if str.IsIPv4(target) {        scanIPTargets(target)    } else {        log.info("start to handle domain: %v", target)        scanDomain(target)    }}
wg := sync.NewWaitGroup()wg.Add(1)go func {    defer wg.Done()    scanPortWg.Wait()}wg.Add(1)go func{    defer wg.Done()    scanDomainWg.Wait()}
wg.Wait()

我们发现,我们想要的流程图中的功能,基本已经完成差不多了,核心功能完成了,难道最简单的把文件保存在本地会是一件难事吗?

显然不是!

0x05 如何把保存到本地文件?#

涉及到文件读写,我们需要使用 yak 内置的文件读写库 file

这个库是在 Golang 原生的文件读写上包裹出来的接口,非常简单易用

内部依赖模块#

  1. file 文件读写

Show Me the Code!#

// 从命令行获取文件名,如果用户没有输入,我们自定义一个随机文件名domainFileName = cli.String("domain-result", cli.setDefault(sprintf("yakmisc-domain-result-%v.txt", randstr(15))))
// 打开/创建这个随机的文件名domainResult, err = file.Open(domainFileName)
// 如果打开文件失败,则推出脚本die(err)
// 把 domainResult 文件及时关闭defer domainResult.Close()

0x06 最终形态 96 行!#

当我们把所有上述细节代码整合在一起的时候,我们发现,仅仅 96 行,我们就实现了从子域名到端口扫描的全部功能。

yakmisc.yak
log.setLevel("info")
resultFileName = cli.String("port-result", cli.setDefault(sprintf("yakmisc-port-result-%v.txt", randstr(15))))domainFileName = cli.String("domain-result", cli.setDefault(sprintf("yakmisc-domain-result-%v.txt", randstr(15))))portResult, err = file.Open(resultFileName)die(err)defer portResult.Close()domainResult, err = file.Open(domainFileName)die(err)defer domainResult.Close()
targets := cli.String("target", cli.setHelp("扫描目标"))scanTargets := str.ParseStringToHosts(targets)if len(scanTargets) <= 0 {    die("empty target is not allowed")}
ports = cli.String("ports", cli.setDefault("80,8080-8082,443"))scanPortWg := sync.NewSizedWaitGroup(10)
// 处理指纹扫描的结果handleTarget = func(result) {    log.debug("found result: %v", result.String())    if result.IsOpen() {        log.info("scanned fingerprint %v", result.String())        portResult.WriteLine(sprintf("%24s   %6s    %s", str.HostPort(result.Target, result.Port), parseStr(result.State), result.GetServiceName()))    }}
scanIPTargets = func(targetIP) {    scanPortWg.Add()
    go func(){        defer scanPortWg.Done()
        log.info("start to handle target ip: %v", targetIP)        res, err := servicescan.Scan(targetIP, ports)        if err != nil {            log.error("servicescan %v ports:%v failed: %s", targetIP, ports, err)            return        }
        for result := range res {            handleTarget(result)        }    }()
}
scanDomainWg := sync.NewSizedWaitGroup(10)scanDomain = func(domain) {    scanDomainWg.Add()    go func{        defer scanDomainWg.Done()        defer fn{            err = recover()            if err != nil {                log.error("panic from scan Domain", err)            }        }        log.info("start to find subdomain for: %v", domain)        res, err := subdomain.Scan(domain, subdomain.recursive(true))        if err != nil {            log.error("subdomain scan[%s] failed: %s", domain, err)            return        }        for result := range res {            result.Show()            domainResult.WriteLine(result.ToString())            scanIPTargets(result.IP)        }    }}
for _, target := range scanTargets {    if str.IsIPv4(target) {        scanIPTargets(target)    } else {        log.info("start to handle domain: %v", target)        scanDomain(target)    }}
wg := sync.NewWaitGroup()wg.Add(1)go func {    defer wg.Done()    scanPortWg.Wait()}wg.Add(1)go func{    defer wg.Done()    scanDomainWg.Wait()}
wg.Wait()

当我们完成上述代码的时候,执行了扫描过程之后,本地生成了如下文件:

-rwxr-xr-x   1 v1ll4n  staff   6.9K  7  1 15:36 yakmisc-domain-result-QeIYGjpFijYzKCM.txt-rwxr-xr-x   1 v1ll4n  staff   3.6K  7  1 15:36 yakmisc-port-result-GnSXvODjEJrZHPn.txt-rw-r--r--   1 v1ll4n  staff   2.5K  7  1 15:31 yakmisc.yak

我们的扫描结果已经得到了。

0x07 结语#

在我们编写上述代码的时候,我们会很明显感觉到我们的编写过程就像搭积木一样,并不是从零开始创造。

Yak 在巨人肩膀上诞生,用户就可以看得更远。