实战1: 扫端口/子域名+指纹识别
本届我们将学习的知识量非常大,这可能是很多开源项目的核心功能,我们要做的就是要把这些功能的实现成本降低。
之前我们要完成这些功能,可能需要一个语言的很多的库,然而在 Yak 中,这些全部被封装在了一个个的内置模块中。
#
信息收集流程设计这个流程虽然也并没有什么特殊的,也并没有任何特色,甚至大家可以认为这是一件很简单的事情,但是实际上,市面上想要找到一个这么简单的流程,也并不是一件容易的事情。
很多优秀的工具,只实现了其中的一部分,而且和其他工具的配合/联动,并不是特别的舒服。
于是市面上出现了一大批缝合项目,但是缝合项目也是有代价的,我们需要安装依赖,或者把整个项目运行在 docker 中。由于很多用户缺乏 docker 使用经验,也懒得去安装语言以及对应依赖 导致这些项目运行的效果并不够理想。
然而在 yak 中,上述问题都得到了一定程度上的解决。
#
0x01 处理用户输入#
内部依赖模块我们内部依赖的模块并不需要用户去导入,直接使用即可!
cli
:可以处理用户从命令行输入的参数。str
:常见处理字符串的模块,用于解析扫描目标,判断字符串内容类型等操作。
#
Show Me the Code!我们在处理用户输入的时候,往往需要使用 cli
这个专门用来处理用户输入的包
文档在这里 cli
官方手册
我们根据文档编写了如下代码,
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 进行端口扫描#
内部依赖模块cli
接收输入的端口参数servicescan
服务指纹扫描
#
Show Me the Code!当我们可以顺利接受目标之后,扫描端口其实是一个非常简单的事情,在我们之前的小课程中,我们已经学会了如何使用 servicescan
和 synscan
了。
与此同时,我们也学习过如何编写 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 进行扫描了,但是这还不够,我们希望可以针对域名进行子域名收集相关的工作:
#
内部依赖模块#
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
当然,你需要记得把扫描目标换成真正你想要的扫描目标
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 原生的文件读写上包裹出来的接口,非常简单易用
#
内部依赖模块#
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 行,我们就实现了从子域名到端口扫描的全部功能。
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 在巨人肩膀上诞生,用户就可以看得更远。