资产搜集
子域名
https://cloud.tencent.com/developer/article/1681277
oneforall分析
https://blog.csdn.net/m0_58434634/article/details/120447411
oneforall除提供oneforall.py这个入口文件外, 提供了三个常用入口
brute.py 用于爆破子域名
export.py 用于导出数据
takeover.py #子域接管漏洞 这个不收集子域名
概括oneforall核心流程如下
run======>检测泛域名=====>收集模块(Collect)=====>SRV爆破模块(BruteSRV)=====>爆破模块(Brute)=====>dns验证(run_resolve)=====>http验证(run_request)======>子域爬取模块(Finder)=====>子域置换模块(Altdns)=====>丰富输出(Enrich)=====>子域接管模块(Takeover)=====>return
泛域名
这里的检测泛域名方法为:生成3个随机域名,如果dns验证和http验证均成功,则判断页面是否相同,如果页面相似就是泛域名;如果dns验证均成功但http验证不成功,那么这个域名也是泛域名;否则不是泛域名。
def to_detect_wildcard(domain):
"""
Detect use wildcard dns record or not
:param str domain: domain
:return bool use wildcard dns record or not
"""
logger.log('INFOR', f'Detecting {domain} use wildcard dns record or not')
random_subdomains = gen_random_subdomains(domain, 3)
if not all_resolve_success(random_subdomains):
return False
is_all_success, all_request_resp = all_request_success(random_subdomains)
if not is_all_success:
return True
return any_similar_html(all_request_resp)
处理泛域名的方法:根据所有域名解析后ip
和cname
出现的次数来判断,如果一个子域名的ip或cname的出现次数超过设置的阈值,则丢弃这个子域名
def check_valid_subdomain(appear_times, info):
ip_str = info.get('ip')
if ip_str:
ips = ip_str.split(',')
for ip in ips:
ip_num = appear_times.get(ip)
isvalid, reason = is_valid_subdomain(ip=ip, ip_num=ip_num)
if not isvalid:
return False, reason
cname_str = info.get('cname')
if cname_str:
cnames = cname_str.split(',')
for cname in cnames:
cname_num = appear_times.get(cname)
isvalid, reason = is_valid_subdomain(cname=cname, cname_num=cname_num)
if not isvalid:
return False, reason
return True, 'OK'
收集模块(Collect)
动态加载certificates, check, datasets, dnsquery, intelligence, search文件夹中的模块查询域名这些模块都是一些能一次查询出多个子域名的模块,且模块运行时间相对较少,可进行快速查询,同时使用了多线程加快扫描速度。
certificates:利用证书透明度收集子域模块 使用在线证书查询接口查询
check:常规检查收集子域模块 例如检测网站证书 robots.txt crossdomain等
datasets:在线子域名查询接口
dnsquery:利用DNS查询收集子域模块
intelligence:利用威胁情报查询
search:利用搜索引擎查询 如google baidu fofa zoomeye等
看一下谷歌搜索
def search(self, domain, filtered_subdomain=''):
"""
发送搜索请求并做子域匹配
:param str domain: 域名
:param str filtered_subdomain: 过滤的子域
"""
page_num = 1
per_page_num = 50
self.header = self.get_header()
self.header.update({'User-Agent': 'Googlebot',
'Referer': 'https://www.google.com'})
self.proxy = self.get_proxy(self.source)
resp = self.get(self.init)
if not resp:
return
self.cookie = resp.cookies
while True:
self.delay = random.randint(1, 5)
time.sleep(self.delay)
self.proxy = self.get_proxy(self.source)
word = 'site:.' + domain + filtered_subdomain
payload = {'q': word, 'start': page_num, 'num': per_page_num,
'filter': '0', 'btnG': 'Search', 'gbv': '1', 'hl': 'en'}
resp = self.get(url=self.addr, params=payload)
subdomains = self.match_subdomains(resp, fuzzy=False)
if not self.check_subdomains(subdomains):
break
self.subdomains.update(subdomains)
page_num += per_page_num
if 'start=' + str(page_num) not in resp.text:
break
if '302 Moved' in resp.text:
break
这段代码通过几个关键策略来模拟人类的行为,以避免被搜索引擎检测到为自动化脚本。这些策略包括随机延时、使用代理和模拟浏览器请求。
while True:
self.delay = random.randint(1, 5)
time.sleep(self.delay)
目的:通过随机延时,模仿人类用户在不同搜索之间的不规则停顿。
实现:使用random.randint(1, 5)生成1到5秒之间的随机数,并使用time.sleep(self.delay)暂停代码执行相应的秒数。
效果:防止搜索引擎识别出固定的请求间隔,从而降低被识别为机器人请求的风险。
self.proxy = self.get_proxy(self.source)
目的:通过使用不同的代理IP地址,避免来自同一IP地址的大量请求被搜索引擎封锁。
实现:调用self.get_proxy(self.source)方法获取新的代理IP,并在后续请求中使用该代理。
效果:分散请求的源IP地址,使得请求看起来像是来自不同的用户,进一步降低被检测的可能性。
分步处理搜索请求,避免一次性发送大量请求。
实现:在每次循环中,构造查询参数并发送请求,解析响应内容,检查是否有新的子域名,更新子域名集合。如果没有新的子域名或遇到特定条件(如搜索结果不再包含新的页码或返回302跳转),则退出循环。
效果:逐步发送请求和解析响应,使得行为更像人类用户逐页浏览搜索结果,同时通过退出条件避免不必要的请求。
word = 'site:.' + domain + filtered_subdomain
payload = {'q': word, 'start': page_num, 'num': per_page_num,
'filter': '0', 'btnG': 'Search', 'gbv': '1', 'hl': 'en'}
resp = self.get(url=self.addr, params=payload)
subdomains = self.match_subdomains(resp, fuzzy=False)
if not self.check_subdomains(subdomains):
break
self.subdomains.update(subdomains)
page_num += per_page_num#下一次搜索的页码增加
if 'start=' + str(page_num) not in resp.text:
break 结束的条件
if '302 Moved' in resp.text:
break
SRV爆破模块(BruteSRV)
这个收集方式还是是第一次遇见,原理是 通过爆破常见的SRV前缀来获取域名
SRV记录是dns记录的一种,一般是为Microsoft的活动目录设置时的应用。DNS可以独立于活动目录,但是活动目录必须有DNS的帮助才能工作。为了活动目录能够正常的工作,DNS服务器必须支持服务定位(SRV)资源记录,资源记录把服务名字映射为提供服务的服务器名字。活动目录客户和域控制器使用SRV资源记录决定域控制器的IP地址。
查询SRV记录的方式:
nslookup -q=srv _sip._tcp.zonetransfer.me #windows
dig srv _sip._tcp.zonetransfer.me #linux
爆破模块(Brute)
爆破模块主要利用massdns工具来爆破,在这里会进行一次更严格的泛域名检测
执行流程简单来说就是首先检测是不是泛域名,生成字典,通过massdns工具爆破,再然后根据结果生成字典递归爆破二级以上,最后处理泛域名
dns验证(run_resolve)
通过call_massdns函数调用massdns进行dns验证,这里会将爆破模块扫描出来的子域名再次扫描一次
http验证(run_request)
将收集到的域名和配置文件中设置的端口生成url,使用requests访问url并验证,记录http请求结果
验证http存活的依据是状态码小于500且不是400
子域爬取模块(Finder)
这里主要通过三种方式寻找子域名
1.find_in_history:从URL跳转历史中查找子域名
2.find_in_resp:从返回内容种查找子域名
3.find_js_urls: 从js中查找子域名
子域置换模块(Altdns) 子域替换技术可根据已有的子域生成输出大量可能存在的潜在子域,利用这些潜在子域名进一步获取存活子域名,目前oneforall中主要有5种置换方式,生成规则如下:
increase_num: test.1.foo.example.com -> test.2.foo.example.com, test.3.foo.example.com, ...
decrease_num: test.4.foo.example.com -> test.3.foo.example.com, test.2.foo.example.com, ...
insert_word: test.1.foo.example.com -> WORD.test.1.foo.example.com,test.WORD.1.foo.example.com,test.1.WORD.foo.example.com,...
add_word: test.1.foo.example.com ->test-WORD.1.foo.example.com #WORD 替换为字典
replace_word: WORD1.1.foo.example.com -> WORD2.1.foo.example.com,WORD3.1.foo.example.com,WORD4.1.foo.example.com,...
在生成子域名后该模块会对生成的潜在子域名进行dns验证和http验证,利用的也是massdns和requests。
丰富输出(Enrich)
增加cidr,asn,org,addr,isp等内容,只是丰富结果,并不会收集新的子域名。
子域接管模块(Takeover)
检测是否存在子域名接管漏洞,不收集新的子域名,具体可参考以下两篇文章
https://zhuanlan.zhihu.com/p/136694063
https://zhuanlan.zhihu.com/p/137001519
oneforall存在的一些问题
1.对子域名比较多的域名进行http验证时耗时过长,容易卡死
2.根据流程可发现,如果选择不进行http验证,则收集流程在爆破完成就结束了,后面的泛域名处理,域置换等模块完全不会执行
3.主流程里的泛域名处理方式和爆破里面的处理方式并不一样,主流程只是用出现cname,ip的次数来校验泛域名的,并没有爆破模块那么严格,所以如果阈值不合理,还是有可能多出来一些无效域名
4.递归爆破的问题,如果a.b.xxx.com存在,b.xxx.com不存在,这种情况用递归爆破是扫不出来的
5.只使用了A记录来获取域名,可能会丢失一部分域名
6.没有统一dns服务器,爆破模块和dns验证模块使用的dns服务器可能不一致
互联网收集
SRV记录查询
SRV记录(Service Record)是DNS(域名系统)的一种资源记录类型,用于定义指定服务在某个域中的位置。SRV记录包含信息,如服务的主机名、端口号、优先级和权重。这使得服务能够灵活地分配和定位,而不需要硬编码到特定的服务器或IP地址。
SRV记录的典型用途包括:
- 即时消息和语音服务:例如,SIP(Session Initiation Protocol)和XMPP(Extensible Messaging and Presence Protocol)。
- LDAP(轻量级目录访问协议):用于查找目录服务的服务器。
SRV记录的格式
SRV记录的格式如下:
kotlin
复制代码
_service._proto.name. TTL class SRV priority weight port target
每个字段的含义如下:
- service:服务名称,通常以
_
开头,例如_sip
表示SIP服务。 - proto:协议类型,通常为
_tcp
或_udp
。 - name:域名。
- TTL:生存时间,DNS记录的缓存时间。
- class:通常为
IN
(表示Internet)。 - SRV:资源记录类型。
- priority:优先级,数值越低优先级越高。
- weight:权重,用于在具有相同优先级的服务器之间进行负载均衡。
- port:服务端口号。
- target:提供服务的主机名。
示例
一个SRV记录示例如下:
yaml
复制代码
_sip._tcp.example.com. 3600 IN SRV 10 60 5060 sipserver.example.com.
这条记录表示:
- 服务是SIP(Session Initiation Protocol)。
- 使用TCP协议。
- 属于域
example.com
。 - TTL为3600秒。
- 优先级为10。
- 权重为60。
- 端口号为5060。
- 提供服务的主机名为
sipserver.example.com
。
HTTPS证书透明度
证书透明度(Certificate Transparency,简称CT)是由Google发起的一个项目,旨在提高HTTPS证书的安全性和透明度。CT通过创建一个公开的、可审核的日志系统,记录所有已签发的SSL/TLS证书,以便于检测和防止非法或错误的证书签发。
证书透明度的工作原理
- 证书日志:证书颁发机构(CA)将每一个签发的证书记录到一个或多个公开的CT日志中。这些日志是公开可查询的,任何人都可以查看和审核。
- 审计和监控:任何人,包括网站管理员、用户、浏览器等,都可以检查这些日志,验证证书的合法性。浏览器也可以利用这些日志来防止使用未记录的证书。
- 嵌入SCT:证书透明度的证明(Signed Certificate Timestamp, SCT)可以嵌入到证书中,或通过TLS扩展提供。这确保了在TLS握手过程中,服务器提供的证书可以被浏览器验证是否存在于CT日志中。
获取子域名
通过查询证书透明度日志,可以发现某个域名下的所有已签发的SSL/TLS证书,进而获取该域名下的子域名。这是因为每个证书都包含一个或多个域名(包括子域名),这些信息在CT日志中是公开的。
如何从证书透明度日志中获取子域名
- 使用证书透明度日志查询工具:有很多工具和网站提供CT日志查询服务,可以用来查找特定域名的所有证书。例如,crt.sh、Censys、Google Certificate Transparency等。
- API查询:一些服务提供API,可以编程方式查询证书透明度日志。例如,Censys和crt.sh都提供API接口,可以通过编写脚本自动化地查询和获取子域名。
子域名查询接口
一些在线的查询接口
DNS找子域名
-
爆破
简单来说就是爆破子域名字符串,如果DNS返回IP就认为这个子域名存在
对于爆破dns来说,有足够多且快的dns server是关键(爆破一段时间后,可能会有dns不再回应请求)
-
DNS域传送子域名漏洞(DNS Zone Transfers)
https://www.freebuf.com/vuls/275451.html
DNS域传送(Zone Transfer)是DNS服务器之间的一种机制,主要用于同步主DNS服务器和从DNS服务器之间的数据。通过域传送,DNS服务器可以将整个区域文件(包含该域所有DNS记录)复制到从服务器。这在主服务器发生变化时,确保所有DNS服务器都拥有一致的数据。
示例:
dig axfr @ns1.example.com example.com
配置限制:大多数现代DNS服务器默认会限制或禁止未授权的域传送请求。这是为了防止潜在的攻击者获取敏感的DNS信息。
验证:只有被授权的从DNS服务器才能成功进行域传送。未授权的请求通常会被拒绝。
实现
class DNSTransfer(object): def __init__(self, domain): self.domain = domain def transfer_info(self): ret_zones = list() try: nss = dns.resolver.query(self.domain, 'NS') nameservers = [str(ns) for ns in nss] ns_addr = dns.resolver.query(nameservers[0], 'A') # dnspython 的 bug,需要设置 lifetime 参数 zones = dns.zone.from_xfr(dns.query.xfr(ns_addr, self.domain, relativize=False, timeout=2, lifetime=2), check_origin=False) names = zones.nodes.keys() for n in names: subdomain = '' for t in range(0, len(n) - 1): if subdomain != '': subdomain += '.' subdomain += str(n[t].decode()) if subdomain != self.domain: ret_zones.append(subdomain) return ret_zones except BaseException: return []
威胁情报
搜索引擎查询
针对不同的搜索引擎去写针对性的代码,然后从返回的结果去匹配子域名,这个过程中要注意伪装成正常用户,详细参考oneforall里面的写法
爆破
它主要使用massdns工具爆破,这里就引出了面试官另一个题,为什么会配置两个dns?
那我们来说一下massdns这个工具,这个工具其实还是利用dns服务器与子域名字典进行匹配爆破,那他为什么会有两个dns服务器呢,因为一个在国内一个在国外,不在国外使用国外服务器,在国内使用国内服务器;其他因素要参考处理泛解析。
子域名查找
历史查询
页面响应内容中出现的子域名,如js查询
跳转过程中的子域名
泛解析域名
泛解析域名(Wildcard Domain)是一种DNS配置,允许在同一域名下的所有子域名都解析到同一IP地址或一组IP地址。泛解析通常用于简化域名管理,使得不论用户访问哪个子域名,都能指向相同的资源。
在DNS配置中,泛解析域名的记录通常以星号 (*
) 开头,表示匹配所有子域名。例如,假设有一个域名example.com
,其泛解析记录可以配置为:
*.example.com A 192.0.2.1
另一个例子是CNAME记录:
*.example.com CNAME www.example.com
这表示所有子域名都指向www.example.com
。
判断一个域名是否配置了泛解析,可以通过以下步骤:
- 查询多个随机子域名:
- 选择几个随机的、不存在的子域名进行DNS查询。例如,查询
random1.example.com
、random2.example.com
等。
- 选择几个随机的、不存在的子域名进行DNS查询。例如,查询
- 比较解析结果:
- 如果这些随机子域名解析到了相同的IP地址或相同的CNAME记录,则很可能存在泛解析配置。
- 在权威 DNS 中,==泛解析记录的 TTL 肯定是相同的==,如果子域名记录相同,但 TTL 不同,那这条记录可以说肯定不是泛解析记录。
脚本实现:
- 生成三个随机域名,然后挨个匹配,如果页面相同存在泛解析;域名解析成功,http解析不成功也是泛解析;否侧不是泛解析。
- 匹配IP跟Cname次数,超过他设置的值就直接抛弃
验证
DNS验证模块
将结果重新进行DNS解析扫描
HTTP验证模块
这个其实很简单,就是在域名前加http头部进行访问不是500也不是400默认就是存活。