资讯动态

Layer子域名挖掘机4.2实战:字典、线程、泛解析与结果清洗

发布时间:2026/10/26 10:33:54 来源:尧图企业网站定制
简介Layer子域名挖掘机4.2纪念版是一款面向网络安全研究者、渗透测试人员与漏洞排查初学者的子域名收集工具它整合DNS记录、WHOIS信息与爬虫结果快速定位目标主域名下容易被忽视的子域名入口并为后续漏洞扫描与资产梳理提供线索。压缩包体积约4MB共5个文件核心是可执行主程序exe和两个dll运行库另附xml配置说明与dic.txt字典文件双击即可运行无需复杂部署便于在测试环境中随身使用。目前已有714人学习下载说明它在安全工具使用人群中具备一定认可度。借助这份工具读者可以直接上手完成子域名枚举并结合结果中的解析IP与证书信息排查未公开资产从而理解子域名挖掘在防御网络钓鱼、中间人攻击与数据泄露中的实际价值。1. 一个字典跑完就交差的年代过去了手头这份 Layer子域名挖掘机4.2纪念版.zip是个 Windows 下的图形化子域名挖掘工具导入字典、指定 DNS、开线程跑完导出一张清单。它解决的是资产梳理最靠前的那一步——把「我只知道三个域名」扩成「这个目标在解析层面到底暴露了多少入口」。适合做渗透测试前期信息收集、SRC 巡检、内网资产台账补齐的人也适合刚接触网络安全工具、想先把「爆破」这件事跑通的新手。它不干后面的事不做存活判断、不做指纹识别、不做截图归档。所以别指望一个工具出完整报告真正决定产出质量的是字典怎么配、线程开多少、泛解析怎么处理以及结果导出后你怎么洗。2. 子域名爆破在赌什么解析链路、命中率与并发上限2.1 一条查询要经过几道手超时值为什么不能拍脑袋先把链路捋清楚后面所有参数才有依据。工具发出一个xxx.example.com的 A 记录查询先落到本机 stub resolver也就是系统里配的那个 DNS 地址如果本机 hosts 和本地缓存没有命中就往上游递归服务器走递归服务器再迭代问根、问顶级、问权威最后把答案或者 NXDOMAIN 带回来。这条链上有三个地方会拖慢你一是递归服务器自己带了缓存热门域名第一次慢、后面快冷门域名每次都慢二是权威服务器对「不存在的子域」的响应速度完全看对方配置有些权威服务器对 NXDOMAIN 要磨三五秒才回甚至直接不回三是你本机到递归服务器之间的链路质量。这就引出一个很容易翻车的点超时值设小了慢响应会被工具当成「不存在」直接丢掉。你在界面上看到跑得飞快其实是把一大批真实存在的子域判成了超时。我一般会把超时先设 5 秒跑一小段观察日志里 timeout 和 NXDOMAIN 的比例如果 timeout 占比超过 20%先别急着加大字典问题在解析侧不在字典侧。还有一个细节NXDOMAIN 是会被负缓存negative caching的TTL 由权威服务器返回的 SOA 记录决定。这意味着你短时间内对同一个不存在的前缀重复查询第二遍拿到的可能是缓存里的快速否定。这对爆破本身没坏处但会让你误判「这条链路很快」——换一批新前缀立刻就慢了。所以压测连接质量要用随机前缀不能用跑过一遍的旧字典。2.2 字典才是真正的产能前缀分层与组合策略线程和工具有天花板字典没有。同样一个目标用 1000 条的通用字典和用一份按行业定制过的 2 万条字典产出可能差三倍以上。我的做法是分三层。第一层是高频通用前缀几十到一两百条用于快速验证链路和泛解析这一层跑完基本能看出目标的大致规模。第二层是业务词比如 OA、CRM、ERP、门户、支付、订单、会员、数据、报表这类中文业务对应的英文或拼音缩写加上环境前缀 dev、test、uat、staging、pre、gray这一层是产出的主力。第三层是组合层把业务词和环境词、数字后缀做笛卡尔积量会爆掉我一般控制在万级再多就跑不动了。三层字典的生成不用手工拼写个小脚本控制规模# gen_dict.py 生成三层字典输出直接可导入的纯文本 base [www, mail, oa, erp, crm, api, admin, portal, pay, order, member, data, report, static, img] env [dev, test, uat, stage, pre, gray, beta, demo] suffix [, 1, 2, 01, 02, new, old] # 第一层基础前缀量小先跑 tier1 list(dict.fromkeys(base)) # 第二层业务词 环境词两种拼接顺序都保留 tier2 set() for b in base: for e in env: tier2.add(f{b}-{e}) # 连字符风格 tier2.add(f{e}-{b}) tier2.add(f{b}{e}) # 紧贴风格很多单位混用 # 第三层再叠加数字后缀控制在上万条量级 tier3 set() for b in base: for s in suffix: if s: tier3.add(f{b}{s}) tier3.add(f{b}-{s}) for name, data in ((tier1.txt, tier1), (tier2.txt, tier2), (tier3.txt, tier3)): with open(name, w, encodingutf-8) as f: f.write(\n.join(sorted(data))) print(name, len(data))这段脚本的产出是三个文件字典里一行一个前缀、不带点、不带域名后缀绝大多数同类工具都是这个格式。dict.fromkeys用来保序去重保证第一层跑的时候顺序稳定、便于对照日志。tier2用 set 是因为组合后顺序无所谓去重更重要。连字符和紧贴两种拼法都要留实际环境里两种都存在少写一种就少一批命中。提示字典里不要放www.、mail.这类带尾点的写法也不要放整域名。导入前用type tier1.txt或者文本编辑器检查一遍有没有隐藏的空行和 BOM带 BOM 的文件在很多工具里第一条会解析失败这种问题排查起来很费时间。2.3 线程数不是越大越好QPS、限速与重试的三角关系图形界面里那个线程数输入框是新手最容易拉满的地方。开 200 线程看着很快实际结果往往是大量 SERVFAIL 和 timeout产出反而比 20 线程少。原因不复杂你面对的不是一台机器而是一台或者几台递归服务器。公共递归服务对单来源 IP 基本都有 QPS 限制超了之后要么返回 SERVFAIL要么直接丢弃不响应。运营商递归服务器还有连接数上限。一旦触发限速你的工具看到的是「超时」和「域名不存在」在界面上往往显示成不同颜色但在导出结果里都是空——这就是漏报的来源。比较稳的组合是线程 20 到 50单次超时 3 到 5 秒重试 1 次。这个组合下单机 QPS 大致在每秒几十到一百多次绝大多数递归服务器扛得住。如果你的目标域名权威服务器本身响应慢把超时拉到 5 秒、线程降到 20用时间换准确率。更彻底的办法是本地起一个递归解析服务把上游缓存和并发都握在自己手里工具的 DNS 指向127.0.0.1。这样查询命中缓存时几乎是内存速度同时能避免被公共递归限速。搭法这里不展开思路是用一个本地递归服务做前置工具只管发查询限速和重试逻辑交给本地这一层。判定当前线程是否过载看三个指标就够timeout 占比、SERVFAIL 占比、单位时间有效解析数。timeout 超过 15% 就降线程SERVFAIL 出现成片就先换 DNS 再谈其他。这个判断标准比任何教程里的固定数字都靠谱因为目标不同、链路不同合适的值只能现场看。3. 把 4.2 跑起来字典、线程、DNS、泛解析四组参数3.1 首次试跑用 200 条字典验证链路别一上来就丢两万条字典进去跑两个小时再发现结果全是空的。第一次跑永远用小字典目的不是出结果是验证三件事DNS 通不通、字典格式对不对、泛解析有没有开。用tier1.txt里截前 200 条加两三条你确定存在的比如主站的 www作为正样本再加三条明显不存在的随机串作为负样本。跑完看结果正样本必须命中负样本必须不出现。正样本没出来问题在 DNS 或者网络负样本出来了说明目标开了泛解析得先处理再谈真爆破。这一步花五分钟能省掉后面两小时白跑。3.2 线程、超时、重试给出可复现的数值下面这张表是我在不同网络环境下反复试出来的起点值不是标准答案但可以作为调参的基线参数保守值激进值调整依据线程数2050timeout 占比超过 15% 就往回调单次超时5 秒3 秒权威服务器慢就加大重试次数12重试会放大 QPS慎用DNS 服务器本地递归就近公共递归见 2.3 的说明泛解析过滤开开建议永远开见 3.3字典规模200 条试跑万级正式跑分批比一次跑完更好排错保守值适合目标权威服务器响应慢、或者你只有公共递归可用的情况激进值适合本地递归已经搭好、链路干净的情况。表里没有「线程 500」这种选项不是保守是那个值在实际环境里的产出大概率低于 20 线程。重试这一项要单独说重试 2 次意味着每条查询在最坏情况下会发三次包等效 QPS 是账面的三倍。看起来提高了命中率实际上更容易触发限速反而把整批结果拖成超时。我的习惯是重试只开 1 次把稳定性问题交给 DNS 服务器选择来解决。3.3 泛解析不处理的话整张表都是废物泛解析是指*.example.com被配置成解析到同一个地址任何随机前缀都能得到答案。这时候工具会告诉你「全部命中」你兴冲冲导出上千条实际一条真的都没有。检测办法很简单生成若干个高熵随机前缀8 到 10 位小写字母加数字逐个查询只要有任意一个解析成功就判定目标开了泛解析。下面这段脚本用的就是 dnspython需要先pip install dnspython# wildcard_check.py 检测泛解析并采样通配地址集合 import random import string import dns.resolver DOMAIN example.com # 换成你的目标域 ROUNDS 5 # 采样轮数多轮才能拿到地址集合 resolver dns.resolver.Resolver() resolver.nameservers [127.0.0.1] # 指向本地递归或就近公共 DNS resolver.timeout 5 resolver.lifetime 5 def rand_label(n10): return .join(random.choices(string.ascii_lowercase string.digits, kn)) wildcard_ips set() for _ in range(ROUNDS): host f{rand_label()}.{DOMAIN} try: ans resolver.resolve(host, A) for r in ans: wildcard_ips.add(str(r)) print(命中通配:, host, [str(r) for r in ans]) except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): pass # 正常说明没有泛解析 except dns.resolver.NoNameservers: print(解析器异常先修 DNS:, host) except dns.exception.Timeout: print(超时检查超时值和 DNS:, host) print(通配地址集合:, wildcard_ips or 无未开启泛解析)ROUNDS设 5 是为了多采样有些泛解析会返回一组轮询地址只查一次拿不全。脚本跑完如果wildcard_ips非空就把这些 IP 记下来回到工具里打开泛解析过滤把地址填进黑名单。工具侧的过滤逻辑通常是「解析结果全部落在通配集合内就丢弃」所以黑名单必须填全少一个 IP 就会漏掉一批假结果。注意有些目标的泛解析不是返回固定 IP而是返回一个 CNAME 再跳到别处。这种情况 IP 黑名单过滤不干净需要人工抽几条看解析链确认是通配之后改用「关键词白名单」的方式反向筛——只保留名字里带业务词的结果。3.4 导出字段与文件组织导出建议选带状态和 IP 的格式别只导域名。文件命名按「目标标识_字典版本_时间戳」来比如targetA_tier2_20250101.txt。看起来是小事等你手上有五六个目标的十几份结果混在一个文件夹里回头找「哪份是全量、哪份是试跑」能让人抓狂。4. 结果不止是一张列表去重、存活、指纹三段式4.1 归一化去重先统一格式再谈合并多轮爆破的结果合并时重复、大小写混用、带尾点、带通配符前缀的条目一定会出现。不统一格式直接合并你会在后面每一个环节都重复处理同一批脏数据。# merge_dedup.sh 合并多份导出的域名列表并归一化 cat raw_*.txt \ | tr A-Z a-z \ # 统一小写域名不区分大小写 | sed s/^[[:space:]]*//;s/[[:space:]]*$// \ # 去掉首尾空白 | sed s/\.$// \ # 去掉尾点example.com. 和 example.com 是同一个 | grep -v ^\* \ # 丢掉通配记录本身 | grep -E ^[a-z0-9]([a-z0-9.-]*[a-z0-9])?\. \ # 粗筛合法域名格式 | sort -u \ merged.txt wc -l merged.txttr和sed的顺序不能反先小写再去空白否则大写带空白的行可能被漏掉。grep -v ^\*这一步是必须有导出里经常混着*.example.com这类通配记录它不是资产留着会污染后续所有统计。最后sort -u排序去重同时让结果按字母顺序排列便于人工扫读时分段核查。4.2 存活探测只留能打通的爆破结果是「解析层面存在」不等于「服务层面活着」。上千条结果里真正有 HTTP 服务的往往只有一两成剩下的可能是内部记录、已经下线但没删解析、或者只跑非标准端口。# alive_check.py 批量探活记录状态码、最终地址、响应长度 import concurrent.futures as cf import requests import urllib3 urllib3.disable_warnings() # 探活阶段不校验证书压掉告警 HEADERS {User-Agent: Mozilla/5.0 (compatible; asset-check/1.0)} TIMEOUT 6 WORKERS 20 # 探活并发别太高容易被 WAF 拦 def probe(host): for scheme in (https, http): url f{scheme}://{host} try: r requests.get(url, headersHEADERS, timeoutTIMEOUT, allow_redirectsTrue, verifyFalse) return (host, scheme, r.status_code, r.url, len(r.content)) except requests.exceptions.RequestException: continue # 这个协议不通换下一个 return (host, -, 0, -, 0) if __name__ __main__: hosts [l.strip() for l in open(merged.txt, encodingutf-8) if l.strip()] with cf.ThreadPoolExecutor(max_workersWORKERS) as pool, \ open(alive.txt, w, encodingutf-8) as out: for host, scheme, code, final, size in pool.map(probe, hosts): if code: out.write(f{host}\t{scheme}\t{code}\t{final}\t{size}\n)WORKERS设 20 是经验值探活和目标站点打交道对方可能有 WAF并发太高会被批量拦成 403你会误以为这些资产「活着但是禁止访问」其实是自己被拦了。allow_redirectsTrue保留完整跳转链final字段能帮你看出一批域名是不是都跳到了同一个登录页——这本身就是一个有价值的信号。verifyFalse只是跳过证书校验不代表忽略 HTTPS很多内网资产用的是自签证书校验会让它们全部被判定为不通。输出里的状态码要分层看200 和 30x 是主力401 和 403 恰恰是高价值说明有认证、有东西可看5xx 一般是后端挂了但记录还在可以放低优先级。4.3 指纹与优先级先看哪几条拿到alive.txt之后排序规则我一般这么定403/401 优先有访问控制说明是业务系统其次是 title 里带「后台」「管理」「登录」「控制台」的然后是响应长度异常的可能是默认页或者错误页堆出来的大页面最后才是普通 200。判断 title 不需要复杂工具探活脚本里加一步取r.text前几 KB 用正则抓title就够了。这一步的价值在于把上千条压到几十条让你有限的人工时间花在值得看的地方。5. 避坑与排查五条踩过的血泪记录5.1 解析侧的三条现象一跑了一晚上结果只有零星几条日志里大量超时。原因基本是上游递归服务器限速了或者超时值设得太小被误判。先看 timeout 占比超过 15% 就是这个问题。解决把线程从几十降到 20超时从 3 秒提到 5 秒DNS 换成本地递归重新跑同一段字典做对比。两次结果差异大的那部分就是之前被吃掉的。现象二结果上千条IP 全是一个去掉重复之后只剩一个地址。这是泛解析没过滤。原因可能是黑名单没开也可能是泛解析返回了多个 IP 而你只填了一个。解决用 3.3 的脚本跑 5 轮采样把拿到的地址全部填进黑名单如果还是漏改成人工抽查解析链确认是 CNAME 型泛解析后换白名单策略。现象三明明知道某个子域存在字典里也有就是跑不出来。三种可能一是这个子域只有 AAAA 或者 CNAME 记录没有 A 记录工具只查 A 就会漏二是它挂在另一个域名下比如xx.example.net而不是.com字典的后缀配错了三是本地递归缓存里留着之前那次错误查询的 NXDOMAIN。解决手工用nslookup加-typeCNAME和-typeAAAA各查一遍确认记录类型换后缀重跑必要时清本地缓存。5.2 流程侧的两条现象四导出的清单看着挺长交给别人复核时被打回一半。原因是没有做存活和归一化直接把爆破原始结果当资产表用了。解析存在和服务可用是两回事。解决任何对外交付的清单必须先过 4.1 的归一化、再过 4.2 的探活交付文件里只保留有状态码的条目原始爆破结果另存备查。现象五工具跑到一半界面卡死进度不动强退之后之前的进度全没了。多半是线程开太高把界面线程也拖住了或者字典条数太多内存吃满。解决字典分批次跑每批控制在万级以内跑完一批导出一批线程别超过 50强退前先看有没有中间结果可以导出。这类问题没有后悔药只能靠分批降低单次损失。6. 进阶用交叉验证给结果打可信度分单靠一份爆破字典你永远不知道自己漏了多少。更稳的做法是把爆破结果和另外两类来源交叉一类是公开的证书透明日志很多单位签发证书时会把内部子域一起写进 SAN 字段这些名字不会出现在首页链接里但会留在日志里另一类是历史解析记录类的公开数据源。两类来源和爆破结果合并之后按来源打权重就能得到一张带可信度的清单。我的权重分配很简单证书透明日志里出现过的加 2 分因为证书是真实签发过的历史记录源出现过的加 2 分只有爆破命中的加 1 分同时被两个以上来源命中的再加 1 分。分数只用于排序不用于删除——低分条目照样保留只是排在最后。# score.py 三源合并打分输出按分数降序 from collections import defaultdict weights {brute: 1, ct: 2, hist: 2} # 各来源基础权重 bonus_multi 1 # 多来源交叉的额外加分 sources { brute: merged.txt, # 爆破归一化结果 ct: ct_names.txt, # 证书透明日志里抓到的名字 hist: hist_names.txt, # 历史解析记录里抓到的名字 } score defaultdict(int) hit defaultdict(set) for src, path in sources.items(): try: names {l.strip().lower().rstrip(.) for l in open(path, encodingutf-8) if l.strip()} except FileNotFoundError: continue # 缺哪个源就跳过不中断 for n in names: score[n] weights[src] hit[n].add(src) for n in score: if len(hit[n]) 2: score[n] bonus_multi with open(ranked.txt, w, encodingutf-8) as f: for n, s in sorted(score.items(), keylambda x: (-x[1], x[0])): f.write(f{s}\t{,.join(sorted(hit[n]))}\t{n}\n)weights里给证书和历史记录更高权重是因为这两个来源的「误报」几乎为零——能进证书 SAN 的名字一定是被签发过证书的而爆破命中可能是泛解析残留。hit记录每个名字被哪些来源覆盖输出到第三列方便你判断这条资产的可信度来源构成。FileNotFoundError的容错是必须的实际工作中经常只有一个源可用脚本不能因为缺文件就整个挂掉。排序之后的人工复核顺序也有讲究先看三源全覆盖的高分条目这类基本可以直接进资产台账再看只有爆破命中的重点核对是不是泛解析残留最后看只有历史记录、当前爆破没跑出来的这类往往是已经下线但证书还没过期的老资产价值不稳定但偶尔有惊喜。从那以后我每次跑完爆破导出结果的第一件事不是打开看而是先跑一遍归一化和泛解析校验再决定这批数据值不值得继续往下做。顺序错了后面全是白工。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑