Python异步子域名探测实战:从CT日志到DNS枚举的进阶工具开发
2026/7/29 21:27:21 网站建设 项目流程

1. 项目概述:从脚本小子到专业工具的跨越

上次我们聊了用Python实现子域名探测的基础方法,主要是基于字典的暴力枚举。那篇文章发布后,很多朋友留言说“学到了”、“思路清晰”,但也有人提出:“效率是不是有点低?”、“遇到有防护的网站怎么办?”。问得好,这说明大家已经不满足于“能用”,开始追求“好用”和“稳健”了。这正是我们这次要深入探讨的。

“子域名探测(二)”这个标题,本身就意味着进阶。如果说第一部分是教你造一把能用的“刀”,那么第二部分就是教你如何把这把刀打磨得更锋利、更趁手,甚至给它配上“刀鞘”和“磨刀石”。在实战的信息收集阶段,子域名枚举的广度和深度,直接决定了后续渗透测试的入口点数量。一个被遗漏的子域名,可能就是整个安全防线的突破口。因此,提升探测的自动化、智能化和健壮性,是每个安全从业者或爱好者的必修课。

这次,我们将不再局限于一个简单的requests循环。我们会引入异步并发来大幅提升速度,整合多个数据源(如证书透明度日志、搜索引擎语法)来扩大探测面,并设计完善的错误处理和结果去重机制,让脚本从一个“实验品”蜕变为一个可以融入实际工作流的“工具”。无论你是安全工程师、红队成员,还是对网络空间测绘感兴趣的研究者,这套思路和代码都能为你提供直接的参考价值。我们用的语言依然是Python,因为它生态丰富、库强大,能让我们快速实现想法。

2. 核心思路与架构升级

在第一部分中,我们的脚本结构可以概括为:读取字典 -> 拼接域名 -> 发起HTTP请求 -> 根据状态码判断存活。这个模型简单直接,但瓶颈也很明显:速度慢(同步请求)、覆盖面窄(仅依赖字典)、容错差(网络波动可能导致脚本中断)。

2.1 设计目标与方案选型

本次升级,我们瞄准三个核心目标:

  1. 速度:利用异步I/O(asyncio + aiohttp)或线程池,将网络请求的等待时间重叠起来,实现并发探测,效率提升十倍甚至百倍。
  2. 广度:不单靠字典爆破,引入被动信息源进行交叉验证和补充。我们主要考虑:
    • 证书透明度(CT)日志:很多网站会为子域名申请SSL证书,这些申请记录是公开的宝藏。
    • 搜索引擎聚合:利用公开的搜索引擎接口或语法,搜集已被收录的子域名。
    • DNS记录查询:尝试查询常见的DNS记录类型(如A, AAAA, CNAME, MX),有时能发现非Web服务的子域名。
  3. 健壮性:完善的异常处理、超时控制、重试机制,以及最终结果的自动去重与格式化输出。

为什么选择这样的架构?因为在实战中,时间和信息的全面性是关键。一个目标可能只有几十个常见子域名,也可能有成千上万个。同步脚本在后者面前几乎不可用。同时,仅靠字典会漏掉那些不常见、但确实存在的子域名(例如为某个临时活动创建的campaign2023.target.com),而CT日志和DNS查询恰好能弥补这一点。

2.2 技术栈与工具链

我们将主要依赖以下Python库:

  • aiohttpasyncio:用于实现高性能的异步HTTP/HTTPS请求。这是提升速度的核心。
  • aiodns:用于异步DNS查询,可以快速解析大量域名。
  • requests:虽然我们用aiohttp做并发请求,但在一些简单的、非并发的API调用(如查询某些CT日志接口)时,它依然稳定可靠。
  • tqdm:为我们的循环加上一个美观的进度条,实时了解任务进度,体验更友好。
  • 内置库argparse(处理命令行参数)、json(解析API返回数据)、logging(记录运行日志,便于排查问题)。

注意:异步编程(asyncio)有一定的学习曲线,但其带来的性能收益是巨大的。如果你对异步还不熟悉,可以把本章的代码当作一个很好的学习案例。我们也会尽量解释清楚关键步骤。

3. 模块化实现与核心代码解析

我们将把功能拆分成独立的模块,这样代码更清晰,也便于后期维护和扩展。整个项目结构可以规划如下:

subdomain_scanner_v2/ ├── scanner.py # 主程序入口,协调各个模块 ├── modules/ │ ├── __init__.py │ ├── bruteforce.py # 基于字典的异步暴力枚举 │ ├── ct_logs.py # 查询证书透明度日志 │ ├── search_engines.py # 从搜索引擎聚合数据 │ └── dns_query.py # 异步DNS记录查询 ├── utils/ │ ├── __init__.py │ ├── helpers.py # 通用工具函数(去重、保存等) │ └── logger.py # 日志配置 └── wordlists/ # 存放各种子域名字典文件 └── common_subdomains.txt

3.1 异步暴力枚举模块 (modules/bruteforce.py)

这是传统方法的异步升级版。核心思想是创建一个异步的HTTP客户端会话,然后用这个会话并发地去请求所有可能的子域名。

import aiohttp import asyncio from tqdm.asyncio import tqdm_asyncio from urllib.parse import urljoin async def check_subdomain(session, url, semaphore): """ 检查单个子域名是否存活(返回可访问的URL)。 使用信号量(semaphore)控制并发度,避免对目标服务器造成过大压力。 """ async with semaphore: # 控制并发数 try: # 设置超时,避免单个请求卡住整个程序 timeout = aiohttp.ClientTimeout(total=10) async with session.get(url, timeout=timeout, ssl=False, allow_redirects=True) as response: # 通常2xx和3xx状态码表示存活,401/403等也可能表示服务存在 if response.status < 400: # 返回最终跳转后的URL,更有价值 final_url = str(response.url) return final_url except aiohttp.ClientConnectorError: pass # 连接错误,子域名可能不存在或无法访问 except aiohttp.ClientResponseError: pass # 响应错误 except asyncio.TimeoutError: pass # 请求超时 except Exception as e: # 其他异常记录日志,但不中断程序 # logging.debug(f"Error checking {url}: {e}") pass return None async def async_bruteforce(domain, wordlist_path, max_concurrency=100): """ 异步暴力枚举子域名。 :param domain: 主域名,如 example.com :param wordlist_path: 字典文件路径 :param max_concurrency: 最大并发数 :return: 存活子域名URL列表 """ alive_urls = [] # 读取字典文件 try: with open(wordlist_path, 'r', encoding='utf-8', errors='ignore') as f: subdomains = [line.strip() for line in f if line.strip()] except FileNotFoundError: print(f"[!] 字典文件未找到: {wordlist_path}") return alive_urls print(f"[*] 加载字典完毕,共 {len(subdomains)} 条记录,开始异步枚举...") # 创建TCP连接器,优化连接池 connector = aiohttp.TCPConnector(limit=max_concurrency, ssl=False) # 创建信号量,控制并发数 semaphore = asyncio.Semaphore(max_concurrency) async with aiohttp.ClientSession(connector=connector) as session: tasks = [] for sub in subdomains: # 构造完整的URL,尝试HTTP和HTTPS full_domain = f"{sub}.{domain}" for scheme in ['http://', 'https://']: url = scheme + full_domain # 为每个URL创建一个检查任务 task = asyncio.create_task(check_subdomain(session, url, semaphore)) tasks.append(task) # 使用tqdm包装任务,显示进度 for task in tqdm_asyncio.as_completed(tasks, total=len(tasks), desc="枚举进度"): result = await task if result: alive_urls.append(result) return alive_urls

关键点解析:

  1. 信号量 (asyncio.Semaphore):这是控制“同时进行”的请求数量的关键。如果不加控制,瞬间发起成千上万个连接,可能会被目标封IP,也可能耗尽本地资源。max_concurrency建议设置在50-200之间,根据网络情况和目标承受能力调整。
  2. 连接器 (aiohttp.TCPConnector):它管理底层的TCP连接池,复用连接可以避免频繁的三次握手,进一步提升速度。limit参数和信号量数值保持一致即可。
  3. 异常处理:我们捕获了多种异常。ClientConnectorError通常代表网络不通或域名不解析;TimeoutError代表请求超时。将这些情况静默处理(pass),只收集成功的响应,保证了程序的流畅运行。
  4. HTTPS与重定向:我们同时尝试了HTTP和HTTPS。设置allow_redirects=True让客户端自动跟随跳转,并记录最终的URL(response.url),这对于识别CDN、负载均衡器或隐藏的真实路径非常有用。

3.2 证书透明度日志查询模块 (modules/ct_logs.py)

证书透明度(Certificate Transparency)是一个公开的日志系统,旨在监测和审计SSL证书的签发。我们可以从公开的CT日志接口中,查询为某个主域名及其子域名签发的所有证书。

import requests import json from urllib.parse import urlparse def query_ct_logs(domain): """ 查询证书透明度日志,获取子域名。 使用 crt.sh 的公开API。 :param domain: 主域名 :return: 子域名集合 """ subdomains = set() url = f"https://crt.sh/json?q=%.{domain}&exclude=expired" try: # 注意:这里使用同步requests,因为只调用一次API,且API返回可能较慢。 # 在主程序中,可以将此函数放入线程池以不阻塞主循环。 resp = requests.get(url, timeout=30) if resp.status_code == 200: data = resp.json() for entry in data: # 提取证书中的常见名称(CN)和主题备用名称(SAN) common_name = entry.get('common_name', '') name_value = entry.get('name_value', '') # 处理 common_name if common_name and domain in common_name: # 清理通配符证书 if common_name.startswith('*.'): subdomains.add(common_name[2:]) else: subdomains.add(common_name) # 处理 name_value (SAN字段,可能包含多个域名,用换行分隔) if name_value: for name in name_value.split('\n'): name = name.strip() if domain in name: if name.startswith('*.'): subdomains.add(name[2:]) else: subdomains.add(name) else: print(f"[!] crt.sh API 请求失败,状态码: {resp.status_code}") except requests.exceptions.RequestException as e: print(f"[!] 查询CT日志时发生网络错误: {e}") except json.JSONDecodeError: print(f"[!] 解析crt.sh返回的JSON数据失败") # 过滤掉非目标域名的结果(因为API是模糊查询) filtered_subs = {sub for sub in subdomains if sub.endswith('.' + domain)} return filtered_subs

实操心得:

  • 数据源选择crt.sh是一个聚合了多个CT日志的免费网站,其API简单易用。除此之外,还可以考虑Facebook的CT查询库Google的CT API等,但crt.sh对于个人和小规模使用通常足够了。
  • 数据清洗:API返回的数据可能包含通配符证书(*.example.com)、主域名本身以及其他不相关的域名。代码中通过检查域名结尾(sub.endswith('.' + domain))进行了过滤,这是关键一步。
  • 性能考虑:这个函数是同步的、网络I/O密集型的。如果在主程序中同步调用,可能会阻塞。一个优化方案是使用concurrent.futures.ThreadPoolExecutor将其放入线程池中运行,这样它就不会阻塞异步主循环。

3.3 搜索引擎聚合模块 (modules/search_engines.py)

我们可以利用公开的搜索引擎语法,来搜索已被收录的、属于目标域名的页面。这里以Google Custom Search JSON APISecurityTrails(部分功能免费)为例。请注意,直接爬取搜索引擎页面违反其服务条款且容易被封,使用官方API是正确途径。

import requests import time def query_google_cse(domain, api_key, cse_id, max_results=100): """ 使用Google自定义搜索API查询子域名。 需要先在Google Cloud Platform创建自定义搜索引擎并获取API Key和CSE ID。 """ subdomains = set() base_url = "https://www.googleapis.com/customsearch/v1" query = f"site:*.{domain}" # 搜索语法:site:*.example.com start_index = 1 while start_index <= max_results: params = { 'key': api_key, 'cx': cse_id, 'q': query, 'start': start_index, 'num': 10 # 每页最多10条 } try: resp = requests.get(base_url, params=params, timeout=15) data = resp.json() if 'items' in data: for item in data['items']: # 从返回的链接中提取子域名 link = item.get('link', '') if link: # 简单提取,实际可能需要更复杂的解析 netloc = urlparse(link).netloc if domain in netloc: subdomains.add(netloc) else: # 可能没有更多结果了,或者触发了速率限制 if 'error' in data: print(f"[!] Google API 错误: {data['error'].get('message')}") break # 检查是否还有下一页 if 'queries' in data and 'nextPage' in data['queries']: start_index += 10 time.sleep(1) # 礼貌性延迟,避免请求过快 else: break except Exception as e: print(f"[!] 查询Google CSE时出错: {e}") break return subdomains # 另一个例子:使用SecurityTrails的API(需注册获取API Key) def query_securitytrails(domain, api_key): """查询SecurityTrails的子域名数据(需要API Key)""" headers = {'APIKEY': api_key} url = f"https://api.securitytrails.com/v1/domain/{domain}/subdomains" try: resp = requests.get(url, headers=headers, timeout=15) if resp.status_code == 200: data = resp.json() # SecurityTrails返回的是子域名前缀列表 subdomains = {f"{sub}.{domain}" for sub in data.get('subdomains', [])} return subdomains else: print(f"[!] SecurityTrails API 错误: {resp.status_code}") except Exception as e: print(f"[!] 查询SecurityTrails时出错: {e}") return set()

重要提示:使用搜索引擎API通常有每日免费配额限制,并且需要注册和配置(如Google CSE需要创建项目、启用API、创建自定义搜索引擎)。请务必遵守相关API的使用条款。在脚本中,建议将API Key等敏感信息通过环境变量或配置文件传入,切勿硬编码在代码中。

3.4 异步DNS查询模块 (modules/dns_query.py)

有些子域名可能没有Web服务(HTTP/HTTPS),但存在DNS记录(如邮件服务器MX记录、别名CNAME记录等)。通过DNS查询可以发现它们。

import asyncio import aiodns from tqdm.asyncio import tqdm_asyncio async def query_dns_record(resolver, subdomain, record_type='A'): """ 异步查询指定子域名的特定DNS记录。 """ try: # aiodns 支持 A, AAAA, CNAME, MX, TXT, NS, SOA 等记录类型 result = await resolver.query(subdomain, record_type) # 返回查询到的记录列表 return record_type, subdomain, [r.host if hasattr(r, 'host') else str(r) for r in result] except aiodns.error.DNSError as e: # 查询失败(如NXDOMAIN)返回None return record_type, subdomain, None except Exception as e: # 其他异常 # logging.debug(f"DNS query error for {subdomain} ({record_type}): {e}") return record_type, subdomain, None async def async_dns_enumeration(domain, wordlist_path, record_types=['A', 'AAAA', 'CNAME', 'MX']): """ 异步DNS枚举。 :param domain: 主域名 :param wordlist_path: 字典文件路径 :param record_types: 要查询的DNS记录类型列表 :return: 字典,键为子域名,值为另一个字典{记录类型: 记录值列表} """ results = {} # 读取字典 try: with open(wordlist_path, 'r') as f: subdomain_prefixes = [line.strip() for line in f if line.strip()] except FileNotFoundError: return results # 创建DNS解析器 resolver = aiodns.DNSResolver() tasks = [] for prefix in subdomain_prefixes: sub = f"{prefix}.{domain}" for rtype in record_types: task = asyncio.create_task(query_dns_record(resolver, sub, rtype)) tasks.append(task) # 处理结果 for task in tqdm_asyncio.as_completed(tasks, total=len(tasks), desc="DNS查询"): rtype, subdomain, records = await task if records: # 如果有记录,说明子域名存在 if subdomain not in results: results[subdomain] = {} results[subdomain][rtype] = records return results

注意事项:

  • DNS查询速度极快:相比于HTTP请求,DNS查询的延迟低得多,因此即使并发数很高,对服务器压力也较小,但也要注意不要对公共DNS服务器(如8.8.8.8)进行滥用。
  • 记录类型选择AAAAA记录对应IPv4和IPv6地址,是最直接的。CNAME记录(别名)可能指向另一个域名,这有时能揭示内部架构或第三方服务。MX记录指向邮件服务器,是重要的资产发现点。
  • 结果解析aiodns返回的对象需要根据记录类型提取信息(如A记录的host属性是IP地址)。代码中做了简单处理,实际应用可能需要更精细的解析。

4. 主程序调度与结果整合 (scanner.py)

现在,我们需要一个“大脑”来协调上述所有模块,处理命令行参数,管理任务执行顺序,并整合、去重、输出最终结果。

import asyncio import argparse from modules import bruteforce, ct_logs, search_engines, dns_query from utils.helpers import save_results, remove_duplicates import logging # 配置日志 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) async def main(): parser = argparse.ArgumentParser(description='高级子域名探测工具 v2.0') parser.add_argument('-d', '--domain', required=True, help='目标主域名 (例如: example.com)') parser.add_argument('-w', '--wordlist', default='wordlists/common_subdomains.txt', help='子域名字典文件路径') parser.add_argument('-o', '--output', default='results.txt', help='结果输出文件') parser.add_argument('-c', '--concurrency', type=int, default=100, help='HTTP请求并发数') parser.add_argument('--no-bruteforce', action='store_true', help='禁用暴力枚举') parser.add_argument('--no-ct', action='store_true', help='禁用证书透明度查询') parser.add_argument('--no-dns', action='store_true', help='禁用DNS枚举') # 搜索引擎API参数 parser.add_argument('--google-api-key', help='Google Custom Search API Key') parser.add_argument('--google-cse-id', help='Google Custom Search Engine ID') parser.add_argument('--securitytrails-key', help='SecurityTrails API Key') args = parser.parse_args() all_subdomains = set() # 使用集合自动去重 logger.info(f"开始对目标 {args.domain} 进行子域名探测") # 1. 证书透明度查询 (同步,可放入线程池) if not args.no_ct: logger.info("[*] 正在查询证书透明度(CT)日志...") ct_subs = ct_logs.query_ct_logs(args.domain) all_subdomains.update(ct_subs) logger.info(f"[+] 从CT日志中发现 {len(ct_subs)} 个子域名") # 2. 搜索引擎聚合 (同步,可放入线程池) if args.google_api_key and args.google_cse_id: logger.info("[*] 正在通过Google自定义搜索查询...") google_subs = search_engines.query_google_cse(args.domain, args.google_api_key, args.google_cse_id) all_subdomains.update(google_subs) logger.info(f"[+] 从Google搜索中发现 {len(google_subs)} 个子域名") if args.securitytrails_key: logger.info("[*] 正在通过SecurityTrails查询...") st_subs = search_engines.query_securitytrails(args.domain, args.securitytrails_key) all_subdomains.update(st_subs) logger.info(f"[+] 从SecurityTrails中发现 {len(st_subs)} 个子域名") # 将收集到的子域名作为基础,用于后续的HTTP和DNS检查 # 同时,我们仍然会使用字典进行暴力枚举 # 这里可以做一个合并:将已发现的子域名前缀加入到一个增强字典中,用于DNS枚举 enhanced_prefixes = set() for sub in all_subdomains: prefix = sub.replace(f".{args.domain}", "") enhanced_prefixes.add(prefix) # 3. 异步DNS枚举 if not args.no_dns: logger.info("[*] 开始异步DNS枚举...") dns_results = await dns_query.async_dns_enumeration(args.domain, args.wordlist) # dns_results 是字典,我们需要提取子域名 dns_subs = set(dns_results.keys()) all_subdomains.update(dns_subs) logger.info(f"[+] 通过DNS枚举发现 {len(dns_subs)} 个子域名") # 可以选择将DNS结果也保存下来 save_dns_details(dns_results, 'dns_details.json') # 4. 异步HTTP暴力枚举 (对已发现和字典中的子域名进行存活验证) if not args.no_bruteforce: logger.info("[*] 开始异步HTTP存活验证...") # 这里我们可以选择对 all_subdomains 中的进行验证,也可以继续用字典爆破 # 为了全面,我们依然使用原始字典进行爆破 alive_urls = await bruteforce.async_bruteforce(args.domain, args.wordlist, args.concurrency) # 从存活的URL中提取子域名 alive_subs = set() for url in alive_urls: parsed = urlparse(url) alive_subs.add(parsed.netloc) # netloc 是 hostname:port all_subdomains.update(alive_subs) logger.info(f"[+] 通过HTTP验证发现 {len(alive_subs)} 个存活子域名") save_results(alive_urls, 'alive_urls.txt') # 最终结果去重和保存 logger.info("[*] 整合并去重最终结果...") final_subdomains = remove_duplicates(all_subdomains, args.domain) logger.info(f"[+] 探测完成!共发现 {len(final_subdomains)} 个唯一子域名。") save_results(sorted(final_subdomains), args.output) logger.info(f"[+] 结果已保存至 {args.output}") if __name__ == '__main__': asyncio.run(main())

核心调度逻辑:

  1. 被动收集优先:首先进行CT日志和搜索引擎查询。这些是“被动”信息收集,不直接与目标系统交互,隐蔽性好,且能快速获得一批种子数据。
  2. DNS主动验证:然后进行DNS枚举。DNS查询是主动的,但相对HTTP更温和、更快。它可以发现没有Web服务的子域名。
  3. HTTP主动验证:最后进行HTTP暴力枚举。这是最直接、但也是最“吵闹”和缓慢的方法。放在最后,是因为我们已经通过前两步缩小了范围(可以将发现的子域名也加入待检查列表),并且它用于最终确认Web服务的存活状态。
  4. 结果聚合:每一步发现的结果都添加到一个总的集合(set)中,利用集合的特性自动去重。

5. 常见问题、优化与避坑指南

在实际运行中,你肯定会遇到各种各样的问题。下面是我在开发和测试过程中踩过的一些坑,以及对应的解决方案。

5.1 性能与稳定性问题

问题1:异步并发数设置过高,导致本地端口耗尽或目标服务器拒绝连接。

  • 现象:程序运行一段时间后,出现大量ClientConnectorErrorOSError: [Errno 24] Too many open files
  • 排查:Linux/Mac下可以用ulimit -n查看单进程文件描述符限制。每个TCP连接都会占用一个。
  • 解决
    • 限制并发数:将max_concurrency(信号量和连接器的limit)设置为一个合理的值,如50-200。可以通过参数传入,方便调整。
    • 调整系统限制:临时提高限制ulimit -n 65535(仅当前会话有效)。对于长期使用,可能需要修改系统配置。
    • 使用更优的连接器aiohttp.TCPConnector(limit=0)可以禁用连接限制,但需谨慎使用。

问题2:目标网站有WAF(Web应用防火墙)或速率限制,导致IP被临时封禁。

  • 现象:前期请求正常,后期全部返回403/429状态码或连接超时。
  • 解决
    • 降低并发,增加延迟:在任务循环中,使用asyncio.sleep()随机添加微小延迟(如0.1-0.5秒),模拟人类操作。
    • 使用代理池:集成代理IP池,在请求时随机切换代理。aiohttp支持通过proxy参数设置代理。
    • 设置User-Agent:为ClientSession设置一个常见的、随机的User-Agent头,避免使用明显的爬虫标识。

问题3:DNS查询时,使用默认的本地DNS服务器可能慢或不稳定。

  • 解决:为aiodns.DNSResolver()指定可靠的公共DNS服务器。
    resolver = aiodns.DNSResolver(nameservers=['8.8.8.8', '1.1.1.1', '114.114.114.114'])

5.2 数据准确性与完整性问题

问题4:从CT日志或搜索引擎获取的子域名包含大量无关结果。

  • 现象:结果里出现了otherdomain.comsub.example.com.cn这类不精确的匹配。
  • 解决:加强过滤逻辑。最可靠的方法是检查域名是否以目标域名结尾。但要注意像example.com.cnexample.com的区别。更严谨的做法是使用正则表达式或domainlib这样的库进行解析和匹配。
    import re def is_subdomain(candidate, main_domain): # 确保 candidate 以 .main_domain 结尾,且前面还有点或直接就是main_domain pattern = r'^(.*\.)?' + re.escape(main_domain) + '$' return bool(re.match(pattern, candidate))

问题5:HTTP存活检查误判。

  • 现象:有些服务返回非200状态码(如403, 404),但服务是存在的;有些CDN或WAF对所有不存在的子域名都返回200和一个默认页面。
  • 解决
    • 综合判断:不要只依赖状态码。可以结合响应体长度、标题关键字、特定指纹(如Server头、页面标题)来判断。
    • 对比基线:如果目标有一个统一的404页面,可以事先访问一个肯定不存在的子域名(如random123456.target.com)获取其“基线”响应特征(如标题、特定文本、哈希值),然后在检查其他子域名时与之对比,显著不同则可能存活。
    • 使用HEAD请求:对于只需要判断存活的场景,使用session.head()session.get()更快,因为它不下载响应体。

5.3 工程化与扩展建议

1. 配置化管理:将API Key、默认字典路径、并发数、超时时间等写入一个配置文件(如config.yamlconfig.ini),方便管理和分享,避免硬编码。

2. 结果丰富化:除了保存子域名列表,还可以将每个子域名的信息结构化保存(如JSON格式),包含发现来源(CT/搜索/DNS/暴力)、IP地址、HTTP状态码、标题、Server头、响应长度等。这为后续的资产梳理和漏洞扫描提供了丰富上下文。

3. 模块插件化:将每种探测方法(如virustotal_api,shodan_api,dns_zone_transfer)设计成独立的插件。主程序通过配置文件加载启用的插件。这样扩展新功能只需编写新插件,无需修改核心代码。

4. 分布式与持久化:对于超大型目标(如*.google.com),单机资源可能不足。可以考虑将任务队列(如待检查的子域名列表)放入Redis,由多个工作节点并发消费。结果也存入数据库(如SQLite/MySQL),便于查询和去重。

5. 遵守法律法规与道德规范:这是最重要的一点。仅将此工具用于你拥有书面授权测试的目标,或你自己的资产。未经授权的扫描可能违反《计算机信息网络国际联网安全保护管理办法》等相关法律法规,构成违法行为。务必在合法、合规的范围内使用技术。

最后,这个“子域名探测(二)”项目,从一个简单的脚本演化成了一个具备一定工程化雏形的工具。它体现了在实际安全工作中,如何将多个信息源、多种技术手段有机结合,在效率、广度和深度之间寻找平衡。代码本身是一个起点,你可以根据自己的需求,轻松地添加新的模块(如FofaShodan的API查询),优化过滤算法,或者为其设计一个图形界面。希望这套代码和思路,能成为你自动化信息收集工作流中一块坚实的基石。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询