freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

SubFinder子域枚举源码结构分析
景_ 2024-10-30 18:28:14 88212

SubFinder是一个子域发现工具,可以为任何目标枚举海量的有效子域名。它已成为sublist3r项目的继承者

地址:https://github.com/projectdiscovery/subfinder

本文主要对Subfinder的源码进行学习(如有错误,恳请指正)

一 初始化

Options

入口main,首先调用ParseOptions(),做了一些参数的获取和准备工作

1730262659_6721b683d76c68eec7f35.png!small?1730262659159

获取参数部分

1730262839_6721b7379fc112bdda99a.png!small?1730262838979

往下走最重要的部分是然后判断了provider config 文件是否存在,如果不存在的话创建

1730263032_6721b7f8db1a0bb63d1e5.png!small?1730263032264

遍历passive中的所有需要apikey的source,并且以yaml形式保存

1730263092_6721b834e8df2e2b5d2a0.png!small?1730263092266

NeedKey方法

1730263388_6721b95c5cc481e1447af.png!small?1730263387487


可以看一下AllSources,这里是subfinder对接的所有搜索源(有些源查询需要apikey),这个yaml的作用就是保存key

1730263202_6721b8a2bba5ad936865e.png!small?1730263208967

provider-config.yaml,如果想要获取更多结果,可以将自己的key填入

1730263284_6721b8f4100ae57d202f9.png!small?1730263283165

NewRunner

这里可以看到通过loadProvidersFrom函数解析yaml中的key

1730263543_6721b9f76719fa47e3a7e.png!small?1730263543008

通过Unmarshal来解析

1730264483_6721bda3cd9b96e96aac9.png!small?1730264483165


接着调用了runner.initializePassiveEngine()返回passiveAgent(保存了一个source数组,存储了各个source结构体)

1730263708_6721ba9c04e05fb946693.png!small?17302637071271730264032_6721bbe00d607cce07988.png!small?1730264031770


NewRunner继续向下,这里初始化了source的速率限制,在后期会进行使用

1730271479_6721d8f7b0f299e00dbc8.png!small?1730271479002

1730271466_6721d8eaa1ade69a4e289.png!small?1730271465884


二 子域发现

首先在withValues中创建了一个上下文,走到RunEnumerationWithCtx()

1730271721_6721d9e93f3604c077a03.png!small?1730271720577

简单拼接了一下domain,调用EnumerateMultipleDomainsWithCtx(),同样支持从文件中读取domain

1730271822_6721da4e164cd4cff5858.png!small?1730271821717

遍历每个domain(感觉刚刚用 \n 凭借的操作有点多余)做了一个Ip的正则匹配

1730272035_6721db234afce915f0fb8.png!small?1730272034601

判断是否指定保存文件或者目录,最终都走到了EnumerateSingleDomainWithCtx()

1730272174_6721dbae8c0d98437ff97.png!small?1730272173804

EnumerateSingleDomainWithCtx中通过r.passiveAgent.EnumerateSubdomainsWithCtx实现子域的获取,返回了一个chan(passiveResults),在下面的代码默认将结果遍历存到uniqueMap,并且最后打印到屏幕上

1730279182_6721f70e00c5ec5f1aa41.png!small?1730279181591

1730280184_6721faf8224c99bc83a4a.png!small?1730280183678

打印结果

1730281616_67220090877d75ad29087.png!small?1730281615981


并且为了提高准确率,如果指定移除通配符,会执行这个代码,这块暂且不看,看主要部分的r.passiveAgent.EnumerateSubdomainsWithCtx

//如果开启r.options.RemoveWildcard,将会创建一个任务池,进行Dns查询
	var resolutionPool *resolve.ResolutionPool
	if r.options.RemoveWildcard {
		resolutionPool = r.resolverClient.NewResolutionPool(r.options.Threads, r.options.RemoveWildcard)
		err := resolutionPool.InitWildcards(domain)
		if err != nil {
			// Log the error but don't quit.
			gologger.Warning().Msgf("Could not get wildcards for domain %s: %s\n", domain, err)
		}
	}

EnumerateSubdomainsWithCtx()函数

创建了一个速率的限制器,这个就是上面初始化时提到的

1730283322_6722073a00c32eec38bd9.png!small?1730283321460

1730283352_67220758915dde9105646.png!small?1730283352174


然后创建了一个Session,这个session实际封装了与特定网络请求会话相关的所有配置和状态,之后对source发起请求均通过session进行

1730280039_6721fa671be5b8470ee77.png!small?1730280038734

1730280640_6721fcc088e5fe035e09c.png!small?1730280639961

1730280884_6721fdb42883e51f84f1e.png!small?1730280883747

新建了一个带有超时时间的上下文,循环调用Sources里的source的run方法,并且将resp输出到result

1730281067_6721fe6b206f857bafb78.png!small?1730281066678

可以看一下这些source源的run方法

quake(没有配置key,自动跳过,其实可以直接定义到外面,避免每个source都重复一次这个代码)

1730281194_6721feeaa7f261b59f465.png!small?1730281194236

subdomaincenter

1730281338_6721ff7a4a2d089f43930.png!small?1730281337888

三 值得借鉴和学习的部分

1 是已经搜集好的各种源,可以直接拿来使用

1730281888_672201a0ce7c3b0c410c2.png!small?1730281888266

2 在代码中很多通过上下文(ctx)传输数据,方便了参数的传递,简化了不少代码

3 正如passiveResults这里对chan的处理一样,函数主体一个先返回一个chan,然后协程进行数据处理,代码会看起来非常优雅

4 agent的封装,省去大量重复代码

四 Subfinder的对接

subfinder直接提供了将其当作库函数的实例 ,在example中

1730282590_6722045e2c8f3ff76e9c7.png!small?1730282589865

使用案例:

直接封装个结构体调用即可

1730282649_67220499dfbbd29b12bb2.png!small?1730282649464

最后有对速率限制这方面有部分代码看的不是特别明白,求指点

# 渗透测试 # 网络安全 # web安全 # 数据安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 景_ 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
景_ LV.2
这家伙太懒了,还未填写个人描述!
  • 10 文章数
  • 8 关注者
Burp插件编写(详细教程)——基于2023新版接口
2025-03-05
Java RASP简单实现
2024-11-07
Behinder 冰蝎源码阅读与去特征浅析
2024-11-01
文章目录