高级子域名发现:证书透明度、爬虫与JS文件分析实战

📅 2026/6/30 18:24:10
高级子域名发现:证书透明度、爬虫与JS文件分析实战
1. 项目概述为什么我们需要“高级”子域名发现在网络安全评估、渗透测试甚至是日常的资产梳理工作中子域名发现都是最基础、也最关键的起点。你可能用过subfinder、amass这类工具它们能快速拉出一堆子域名但很多时候你会发现结果要么不全要么充斥着大量无效或过时的记录。这就是“基础”与“高级”的区别所在。基础方法依赖公开的DNS数据集和暴力枚举而高级子域名发现则更像一个侦探它不满足于询问“谁在这里登记过”而是去翻查“谁在这里留下过痕迹”。这个项目标题——“高级子域名发现证书透明度、爬虫与JS文件分析”——精准地指出了三个核心的、能挖掘出“隐藏资产”的线索来源。证书透明度CT日志记录了所有公开签发的SSL/TLS证书里面可能包含从未在公开DNS中解析过的内部域名网络爬虫能模拟真实用户访问从网页的HTML、注释、甚至跳转逻辑中发现未被直接链接的子域名而JS文件分析则是从现代Web应用复杂的JavaScript代码中提取出API端点、第三方服务域名等“动态资产”。把这些手段组合起来你构建的就不再是一个简单的域名列表而是一张目标组织的数字资产地图。这张地图的完整度直接决定了后续安全测试的覆盖面和深度。无论是安全工程师、红队成员还是负责企业资产管理的运维掌握这套方法都意味着你能看到别人看不到的东西。2. 核心思路与方案选型构建一个立体的侦察体系传统的子域名发现工具其数据源相对单一主要依靠被动数据源如VirusTotal、SecurityTrails、Censys等平台聚合的DNS历史记录。字典暴力枚举使用庞大的子域名字典向目标的权威DNS服务器发起查询。 这些方法有效但存在明显短板被动数据可能滞后或遗漏暴力枚举则会产生大量噪音且对没有泛解析的域名效果有限。因此我们的高级方案设计思路是“主动侦察 深度内容挖掘”形成一个立体化的信息收集管道2.1 为什么选择证书透明度CT当网站启用HTTPS时证书颁发机构CA需要将签发的证书信息公开记录到CT日志中。这些日志是公开可查的宝藏。一个为dev.internal.example.com签发的证书即使这个域名从未对外提供Web服务它的记录也会留在CT日志里。这完美解决了“内部系统域名外部不可见”的问题。我们选择使用crt.sh的API或直接查询公共CT日志服务器如Google的pilots作为数据源。它的优势在于数据权威、实时性强能发现大量在常规扫描中遗漏的测试、开发、预发布环境域名。2.2 为什么需要定制化爬虫而非通用爬虫通用爬虫如gospider,hakrawler速度快但“智商”不高。它们主要提取明显的链接href,src。而定制爬虫的核心任务是“理解上下文”。例如从JavaScript代码中提取字符串正则匹配类似api.、ws.、internal.开头的域名模式。解析HTML注释开发人员经常在注释里留下测试地址如!-- 测试环境: https://staging.app.example.com --。处理JavaScript重定向有些子域名只在特定条件下通过JS跳转需要执行JS才能发现。 我们选择用Python的requests/aiohttp库配合BeautifulSoup/lxml进行静态分析并结合Playwright或Selenium进行动态渲染以应对现代前端框架如React, Vue构建的单页应用。这样能确保我们抓取到页面完全加载后的所有内容。2.3 为什么聚焦JS文件分析在现代Web开发中前后端分离是主流。前端JavaScript文件里包含了大量的硬编码URL、API路径、第三方服务域名如CDN、统计、客服系统。这些信息往往不会出现在HTML中却是应用逻辑的重要组成部分。分析JS文件就是直接“翻阅”应用的通信手册。我们不仅要从爬虫下载的JS文件中提取还应主动从类似https://example.com/static/js/main.abc123.js这样的常见路径中寻找或从页面的script标签中收集JS URL进行深度下载和分析。方案整合架构整个流程将设计为一个管道。首先从CT日志获取一批“种子”域名然后用定制爬虫对这些域名的Web服务进行深度抓取从中提取更多子域名和JS文件路径最后对收集到的所有JS文件进行静态分析提取出隐藏的域名和端点。这三个环节相互反馈形成循环不断扩充资产列表。3. 核心模块详解与实操要点3.1 证书透明度CT日志挖掘实战CT日志查询的核心是理解其数据结构和访问方式。最常用的入口是crt.sh网站及其提供的PostgreSQL接口。不过直接调用公共API更便于自动化。实操步骤基础查询使用crt.sh的API构造一个简单的查询来获取为目标域名签发的所有证书。# 使用curl示例 curl -s https://crt.sh/?q%.example.comoutputjson | jq -r .[].name_value | sed s/\*\.//g | sort -u这个命令会查询所有包含.example.com的证书并提取name_value字段即证书中的域名去除通配符*.前缀最后去重排序。处理通配符与SAN字段一个证书可能包含多个域名主题备用名称SAN。crt.sh的JSON输出中name_value字段通常以\n分隔多个域名。我们需要妥善分割。此外通配符证书如*.internal.example.com提示存在一个庞大的子域名空间可以将其作为暴力枚举的字典来源。构建自动化脚本为了提高效率我们可以用Python编写一个模块。import requests import json def query_ct_logs(domain): url fhttps://crt.sh/?q%.{domain}outputjson try: resp requests.get(url, timeout30) resp.raise_for_status() data resp.json() subdomains set() for entry in data: # 处理name_value字段可能是字符串或列表 name_value entry.get(name_value, ) if isinstance(name_value, str): names name_value.split(\\n) else: names name_value for name in names: name name.strip().lower() if name.startswith(*.): name name[2:] # 移除通配符保留根域 # 可以将根域加入列表或根据它生成字典 if domain in name: # 简单过滤确保相关性 subdomains.add(name) return list(subdomains) except Exception as e: print(f查询CT日志失败: {e}) return [] if __name__ __main__: domains query_ct_logs(example.com) for d in domains: print(d)注意事项与心得注意crt.sh的API有速率限制过于频繁的请求会导致IP被临时屏蔽。在脚本中务必加入延时如time.sleep(1)和错误重试机制。心得一数据清洗是关键。CT日志数据很“脏”包含大量过期证书、重复条目、甚至是不相关的域名。提取后一定要进行严格的去重和有效性过滤例如只保留以目标域名结尾的项。心得二关注“关联域名”。证书里可能包含邮箱域名company.com、或其他关联企业的域名。这些虽然不是直接的子域名但对于绘制攻击面非常有价值应单独归类记录。3.2 智能爬虫的设计与避坑指南写一个能用于资产发现的爬虫和写一个数据抓取爬虫侧重点完全不同。我们的目标是“发现”而非“爬取全部内容”因此需要精心设计策略。核心设计要点广度优先与深度限制采用广度优先搜索BFS来最大化覆盖不同路径。同时必须设置爬取深度例如3-4层避免陷入无穷无尽的链接中或者爬取到完全无关的站外链接。遵守robots.txt与伦理在发起请求前先获取并解析目标域的robots.txt文件尊重Disallow规则。这是法律和伦理要求也能避免你的IP被迅速封禁。请求头伪装与速率控制使用常见的浏览器User-Agent如Chrome并添加Referer、Accept-Language等头让请求看起来更像真人。在两个请求之间设置随机延时如1-3秒大幅降低对目标服务器的压力。动态内容渲染对于疑似SPA单页应用的网站静态HTML解析一无所获。此时需要启动无头浏览器如Playwright。from playwright.sync_api import sync_playwright def dynamic_crawl(url): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) # 无头模式 page browser.new_page() page.goto(url, wait_untilnetworkidle) # 等待网络空闲 # 获取渲染后的HTML content page.content() # 也可以执行JS来触发某些事件 # page.click(button#loadMore) browser.close() return content动态渲染资源消耗大、速度慢应仅作为静态爬虫的补充针对性地用于重要或可疑的URL。从内容中提取子域名的技巧正则表达式使用如r[a-zA-Z0-9][a-zA-Z0-9-]*\\.example\\.com的模式进行匹配。注意转义点号并考虑域名中可能包含下划线虽然不符合标准但确实存在。HTML标签属性不仅扫描href和src还要关注action表单提交地址、>import esprima import re def extract_from_js_with_ast(js_code): endpoints set() try: tree esprima.parseScript(js_code, tolerantTrue) # 简化示例遍历AST寻找字面量字符串 for node in esprima.walk(tree): if node.type Literal and isinstance(node.value, str): if re.match(r^/api/[\w/-], node.value): endpoints.add(node.value) # 也可以检查是否包含域名 except Exception as e: print(fAST解析失败降级为正则匹配: {e}) # 降级方案使用正则表达式 endpoints.update(re.findall(r[\](/api/[\w/-])[\], js_code)) return endpoints实操心得心得一分层处理。不要对所有JS文件一视同仁。优先分析来自主域名、且文件名类似app、main、vendor的大型JS文件它们包含核心逻辑的概率更高。来自第三方CDN如cdn.bootcss.com的库文件信息价值相对较低。心得二注意误报。JS中可能包含示例代码、注释掉的URL或用于单元测试的假域名。提取到的所有信息都需要经过一步验证比如尝试解析DNS或发送一个简单的HTTP HEAD请求确认其真实存在。心得三保存上下文。仅仅记录一个域名api.internal.com是不够的。最好能记录它是在哪个主域名的哪个JS文件中被发现的以及其前后的几行代码。这能为后续的漏洞利用或深入测试提供宝贵线索。4. 系统整合与自动化流程实现将三个模块串联起来形成一个自动化的工作流是提升效率的关键。这里我设计一个基于Python的简单框架流程。4.1 整体架构与数据流整个系统可以看作一个“发现-增强-验证”的循环管道。输入一个根域名例如example.com。阶段一初始发现调用CT日志查询模块获取第一批子域名列表A。使用一个基础的子域名字典进行快速DNS枚举获取列表B。合并A和B去重得到初始资产集S。阶段二内容增强对S中所有支持HTTP/HTTPS的域名启动智能爬虫模块。爬虫从页面中提取新的子域名和JS文件URL分别加入集合S_new和JS_urls。将S_new合并回S。阶段三深度挖掘下载JS_urls中的所有JavaScript文件。使用JS文件分析模块从代码中提取隐藏的域名和API端点得到集合S_js和Endpoints。将S_js合并回S。阶段四验证与输出对最终集合S中的所有域名进行简单的存活验证如HTTP状态码200/403/500等或TCP端口扫描。输出结构化的报告包括有效子域名列表、对应的IP地址、开放的服务Web、非Web、发现的API端点、以及每个资产的来源CT/爬虫/JS。4.2 核心代码结构示例# 这是一个简化的主流程示例 import asyncio from modules.ct_scanner import CTScanner frommodules.intelligent_crawler import Crawler frommodules.js_analyzer import JSAnalyzer frommodules.validator import Validator class AdvancedSubdomainDiscoverer: def __init__(self, root_domain): self.root_domain root_domain self.all_subdomains set() self.js_urls set() self.endpoints set() self.ct_scanner CTScanner() self.crawler Crawler(rate_limit1) # 1秒延迟 self.js_analyzer JSAnalyzer() self.validator Validator() async def run(self): print(f[*] 开始对 {self.root_domain} 进行高级子域名发现) # 1. CT日志查询 print([*] 阶段1: 查询证书透明度日志...) ct_subs await self.ct_scanner.query(self.root_domain) self.all_subdomains.update(ct_subs) print(f 从CT日志发现 {len(ct_subs)} 个子域名。) # 2. 初始爬取与发现 print([*] 阶段2: 启动智能爬虫进行内容抓取...) # 这里简化只爬取初始集合中的前N个实际应做任务队列 initial_targets list(self.all_subdomains)[:10] for target in initial_targets: new_subs, new_js_urls await self.crawler.crawl(fhttp://{target}) self.all_subdomains.update(new_subs) self.js_urls.update(new_js_urls) # 3. JS文件深度分析 print([*] 阶段3: 分析收集到的JavaScript文件...) for js_url in list(self.js_urls)[:20]: # 限制分析数量 hidden_subs, found_endpoints await self.js_analyzer.analyze(js_url) self.all_subdomains.update(hidden_subs) self.endpoints.update(found_endpoints) # 4. 验证与输出 print([*] 阶段4: 验证资产存活状态...) validated_assets await self.validator.validate_async(list(self.all_subdomains)) # 生成报告 self.generate_report(validated_assets) def generate_report(self, assets): # 输出报告到文件和控制台 with open(f{self.root_domain}_report.txt, w) as f: f.write(f目标根域名: {self.root_domain}\\n) f.write(f发现子域名总数: {len(self.all_subdomains)}\\n) f.write(f发现API端点总数: {len(self.endpoints)}\\n\\n) f.write( 已验证的存活资产 \\n) for asset in assets: f.write(f{asset[domain]} - IP: {asset[ip]} - 状态: {asset[status]}\\n) print(f[] 报告已生成: {self.root_domain}_report.txt) # 异步主函数 async def main(): discoverer AdvancedSubdomainDiscoverer(example.com) await discoverer.run() if __name__ __main__: asyncio.run(main())4.3 性能优化与工程化考虑异步并发爬虫和网络验证是I/O密集型任务使用asyncio和aiohttp可以极大提升效率同时控制并发连接数避免对目标造成过大压力。任务队列与去重使用Redis或内存中的队列管理待爬取URL并在入队时进行严格去重基于URL规范化后的结果。结果持久化使用SQLite或MySQL存储发现结果记录发现时间、来源、验证状态等便于后续跟踪和增量扫描。配置化管理将目标域名、爬虫深度、请求头、代理设置、关键词字典等参数外置到配置文件如config.yaml中提高灵活性。5. 常见问题、排查技巧与实战心得在实际运行这套系统的过程中你会遇到各种各样的问题。下面是我踩过坑后总结的一些典型场景和解决方法。5.1 数据源失效或受限问题crt.shAPI无法访问或返回空数据。排查首先检查网络连通性。其次直接访问https://crt.sh/?qexample.com看网页是否正常。如果网页正常但API失败可能是API端点临时变更或增加了防护如Cloudflare WAF。解决添加更完整的请求头模拟浏览器。使用其他CT日志查询接口作为备用如censys.io或transparencyreport.google.com提供的视图虽然交互方式不同。在脚本中实现简单的重试机制和退避策略如首次失败等待5秒再试。5.2 爬虫被封锁或收到非预期响应问题大量请求返回403/429状态码或收到验证码页面。排查检查请求头是否完整特别是User-Agent,Accept。检查请求频率是否过高。查看响应内容是否包含“Access Denied”、“Rate Limited”或验证码HTML。解决降低频率这是最有效的方法。将请求间隔从固定值改为随机范围如random.uniform(2, 5)秒。使用代理池如果目标防护严密需要轮换多个IP地址。可以使用一些免费的代理API注意稳定性和安全性或搭建自己的代理服务器。模拟浏览器行为对于需要登录或复杂交互的网站考虑使用Playwright等工具完全模拟浏览器会话包括处理Cookie。设置超时与重试对网络超时设置合理的阈值并对非致命的5xx错误进行有限次重试。5.3 JS分析误报率过高问题提取出大量明显无效的域名如本地地址localhost、示例域名example.com、或其他完全不相关的商业域名。排查检查正则表达式或AST遍历规则是否过于宽松。查看提取到的字符串上下文。解决强化过滤规则建立强大的黑名单过滤掉localhost、127.0.0.1、example.com、test.com等。同时使用白名单模式只保留以目标根域名或其父域结尾的字符串。上下文关联不仅仅提取字符串还尝试分析其所在的代码逻辑。例如一个字符串如果被用在fetch()、XMLHttpRequest或window.location赋值中它是真实URL的可能性就大大增加。人工审核样本定期对提取结果进行抽样检查根据误报案例调整分析规则。5.4 结果庞杂难以聚焦问题最终发现了成百上千个子域名不知道哪些是重点。解决引入“资产优先级评分”机制。来源权重从CT日志发现的、特别是包含api、admin、dev、staging、internal等关键词的域名权重调高。响应特征返回状态码为200、301、302的域名权重高于返回404的。具有独特Title或特殊HTTP头的如X-Powered-By: ASP.NET权重调高。端口与服务除了80/443还开放了其他端口如8080, 8443, 22, 3306的资产需要重点关注。关联性在多个独立来源如CT和JS分析中都出现的域名可信度和重要性更高。最后一点个人体会高级子域名发现不是一个一劳永逸的工具而是一个需要持续调优的过程。每个目标都有其独特性。最好的策略是让工具跑起来拿到初步结果然后人工去分析这些结果为什么这个域名会出现它属于哪个系统通过这种反馈反过来优化你的爬虫规则、JS分析关键词和过滤逻辑。这套方法的真正威力在于将自动化工具的广度与安全研究员的分析深度结合起来最终让你对目标的攻击面有一个远超常人的清晰认知。