简介:Layer子域名挖掘机4.2纪念版面向网络安全研究、渗透测试与漏洞排查人员,聚焦目标站点子域名的快速发现与资产梳理。工具可整合DNS记录、WHOIS信息与爬虫结果等数据源,对主域名关联子域进行匹配筛选,并输出解析IP、证书信息等结果,便于后续指纹识别与风险排查。压缩包为zip格式,共5个文件、约4MB,以exe主程序、dll运行依赖、txt字典和xml配置为主,解压后即可按说明开展扫描。已有714人学习下载,适合具备基础网络与安全知识、希望补齐子域名资产发现环节的读者参考。其价值在于提供一套可直接运行的挖掘流程与字典、配置素材,帮助理解子域名收集的数据来源、结果筛选和导出思路,有助于减少遗漏与重复筛选,并可作为渗透测试前期信息收集的辅助工具。
1. 先别急着扫端口,子域名这一层没挖干净后面都是白做
接手一个主域名做资产梳理,第一步几乎从来不是端口扫描——真正决定后面工作量的,是子域名挖得全不全。一个 www 之外没人记得的 api、test、admin,往往就是整条资产链上最松的那颗螺丝。Layer 子域名挖掘机 4.2 纪念版干的就是这件事:一个绿色包,解压即用,靠内置字典做 DNS 爆破,同时挂接口做被动收集,跑完直接导出清单。它适合做自有资产自查的人、做授权范围内前期信息收集的人,也适合刚入门、想弄明白「子域名枚举到底在枚举什么」的新手。下面按我实际拆包、跑通、踩坑的顺序讲,参数和坑都写具体。只对自有域名或拿到书面授权的目标使用,这一点没有例外。
2. 字典爆破与被动收集:4.2 版把两条腿绑在一起的理由
2.1 先分清哪条路在和目标说话
子域名枚举流传最广的误解,是把它当成一个「查」的动作。实际上它包含两类完全不同的行为:被动收集不发一个包给目标,靠的是证书透明度日志、公开数据集、历史解析记录这些第三方沉淀;主动爆破则是对权威 DNS 一个标签一个标签地问「这个存在吗」,每一次查询都在目标的解析日志里留下痕迹。前者不触发告警、速度快,但覆盖率完全取决于目标曾经在哪暴露过;后者能把「内网命名习惯」这类东西挖出来,代价是慢,而且字典里没有的名字它永远问不到。
我把两类的差别列成一张表,选型时基本看这张表就够了:
| 方式 | 是否直接接触目标 | 覆盖率取决于 | 典型耗时 | 主要短板 |
|---|---|---|---|---|
| 被动收集 | 否 | 目标历史暴露面 | 分钟级 | 新上线、未公开的子域查不到 |
| 字典爆破 | 是 | 字典质量 + 并发策略 | 十分钟到数小时 | 无规律命名的子域必然漏 |
| 两条腿并跑 | 部分 | 两者叠加 | 略高于单跑 | 结果重复率高,必须去重 |
Layer 4.2 的设计取向很明确:输入一个根域名,两条流水线同时走,最后把结果合到一张表里导出。这个取向对实操的意义在于,你不用再手动把被动结果和爆破结果拼一遍——但它也带来一个新问题,合并后的结果集里假阳性和重复项混在一起,第三章和第四章讲的全是这件事。
提示:被动收集拿到的记录有可能是几个月前的,目标下线后解析记录仍留在日志里。判断「还活着」必须再做一次 DNS 复核,不能直接拿历史记录当现存资产。
2.2 解压之后先看什么:包结构与运行环境
这类工具包最容易翻车的地方不是功能,是路径和解压编码。中文文件名在部分解压工具下会变成乱码,主程序读不到同目录的字典文件,表现就是「一跑就结束、结果为空」,很多人第一反应是工具坏了,其实是字典路径没对上。我一般会先建一个纯英文、无空格的目录再解压,把路径问题一次性掐掉。
# 1) 建独立工作目录,避免中文路径和空格导致的读写异常 mkdir -p ~/work/layer42 && cd ~/work/layer42 # 2) 解压;-O CP936 用于修正中文文件名编码 # 若当前 unzip 不支持 -O,改用 7z x 或图形界面解压,不要硬扛 unzip -O CP936 Layer子域名挖掘机4.2纪念版.zip -d ./ # 3) 先看结构再决定怎么跑:主程序、字典、配置、说明文档各在哪 find . -maxdepth 2 -type f | head -50 du -sh ./* # 4) 记下压缩包哈希,隔一段时间复跑时能确认包没被动过 sha256sum Layer子域名挖掘机4.2纪念版.zipfind那一步是给你自己看的:确认字典文件确实和主程序在同一层目录下,或者确认配置里写的字典路径是相对路径还是绝对路径。sha256sum这条看着多余,实际上很值——同一份包在不同机器上传来传去,被替换或损坏的概率不低,出问题时至少能排除「包本身有问题」这个变量。
运行环境上,这类绿色版程序通常是单个 exe 加若干资源文件,Windows 下双击即可。两个务实的建议:一是在虚拟机或独立环境中跑,工具来源不可控时这是最省心的隔离手段,杀软报毒也多半是这类打包程序的误报,但仍要按可疑文件对待;二是首次运行前把工作目录里的文件列一遍,确认没有额外的自启动项被塞进来。
2.3 第一次跑通:从一个根域名到一份清单
完整流程其实就是四步,但每一步都有值得写下来的细节。
第一步,整理输入。工具要的是根域名,不是 URL。很多人直接把浏览器地址栏那串粘进去,带协议带路径带查询串,程序解析失败,结果为空,然后开始怀疑人生。
# 把各种形态的输入统一成「一行一个根域名」 printf '%s\n' 'https://a.example.com/x?y=1' 'B.example.org:8443' \ | sed -E 's#^[a-z]+://##; s#/.*$##; s#:.*$##' \ | tr 'A-Z' 'a-z' | sort -u > roots.txt cat roots.txtsed那两段分别砍掉协议头和路径,第三段砍端口,tr统一小写(域名大小写不敏感,但结果里混着大小写会让后面的去重失效)。输出应该只剩干净的主机名。
第二步,选 DNS 服务器。默认走系统配置的递归服务器最稳,因为延迟低、缓存命中率高;换成公共服务器适合本地网络解析被污染或限速的场景,代价是延迟上升,需要相应放大超时时间。
第三步,选字典、定并发。第一次跑不要上全量字典,用一个几千行的小字典跑通链路,确认有结果、格式对、导出正常,再换大字典。这个习惯能帮你把「工具问题」和「字典/参数问题」分开。
第四步,导出与落盘。导出格式优先选带分隔符的纯文本,方便后面用脚本处理;不要导出成带格式的表格再手工复制,中期维护会很痛苦。
注意:一次跑完就关窗口是常见的坏习惯。结果文件和本次使用的字典、线程数、DNS 服务器要一起留档,否则两周后看到一份清单,你根本判断不出这份结果的置信度有多高。
3. 字典、线程、超时:三个参数决定你是跑全还是白跑
3.1 字典:从十万行砍到八千行,召回反而更高
字典不是越大越好,这句话在子域名爆破里尤其成立。十万行的通用字典里,绝大多数条目对某个具体目标是纯噪音,而每一条噪音都要消耗一次 DNS 查询和一段超时等待。更麻烦的是噪音会把你的注意力淹掉——结果导出来三千条,你根本不知道从哪看起。
我一般做三件事:去重与格式清洗、按业务语义补充、按行业补充。
# 字典清洗:统一小写、去空白、剔除非法标签、限制长度、去重 tr 'A-Z' 'a-z' < raw_dict.txt \ | sed 's/[[:space:]]//g' \ | grep -E '^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$' \ | awk 'length($0) >= 2 && length($0) <= 30' \ | grep -vE '^(www[0-9]*|mail[0-9]*)$' \ | sort -u > dict_clean.txt wc -l raw_dict.txt dict_clean.txt逐段说明。tr统一大小写;sed去掉行内空格,很多字典是从网页复制来的,中间夹着不可见字符;grep -E那条正则是在按 DNS 标签规则过滤:只能由字母、数字、连字符组成,不能以连字符开头或结尾,单段最长 63 字符——不符合这条的条目查了也是白查;awk把长度卡在 2 到 30 之间,单字符标签和超长字符串基本是垃圾;最后那条grep -v是我个人的习惯,把纯数字后缀的 www/mail 系列剔掉,因为这类名字如果存在,被动收集那边基本一定能捞到,不值得占爆破配额。清洗完通常会从十万行掉到一两万行,跑完的时间直接砍掉一大半。
补词比清洗更值钱。按业务补:pay、order、cart、user、passport、open、gateway 这类;按行业补:教育类的 lib、jw、cas,医疗类的 his、lis、pacs;再补一批拼音缩写,很多单位的内网命名就是拼音首字母。补完的这批词命中率远高于通用字典里剩下的部分。
3.2 线程、超时、重试:调错一个就是大面积假阴性
这是整个流程里最容易出隐性错误的地方,因为它的失败方式不是报错,而是「结果变少了」,而你不会知道少了。
| 参数 | 保守取值 | 激进取值 | 影响面 |
|---|---|---|---|
| 并发线程 | 30–50 | 150–200 | 过高导致丢包,被误判为不存在 |
| 单次超时 | 2500–3000 ms | 800–1000 ms | 过低把慢响应当成 NXDOMAIN |
| 重试次数 | 1 | 0 | 0 重试在丢包网络下漏得最狠 |
| DNS 服务器 | 本地递归 | 多个公共轮换 | 公共服务器易被限速 |
| 泛解析过滤 | 开 | 开 | 关掉等于放弃结果可信度 |
关键机制在这里:DNS 主要走 UDP,无连接、不保证送达。并发拉到 200 以上时,本地出口路由器和运营商侧都开始丢包,程序等不到回应就记为「不存在」。于是你得到一份看起来很干净、实际漏了一大片的结果。更麻烦的是这个错误不可复现——换个时间跑,漏的又是另一批。
我的做法是先做一次召回率自检:从已经确认存在的子域里挑 20 个,混进字典里,用你打算用的参数跑一遍,看这 20 个能回来几个。回来 20 个,参数可以放心用;回来 17 个,说明漏了 15%,要么降线程要么加超时。这个自检只要五分钟,能省掉后面几小时的返工。
另一个细节是超时和重试的组合。单次超时设 1000 ms、重试 0 次,等效于「超过 1 秒不回就当没有」,在跨境或跨运营商的链路上这个假设非常激进。反过来,超时 3000 ms 加 1 次重试,最坏情况下一个不存在的名字要占用 6 秒,字典一大就跑不完。折中是超时 2000 ms、重试 1 次,同时对字典按预期命中率分层——高价值词放前面先跑。
3.3 结果清洗:把导出文件变成能进表格的东西
工具导出的东西通常是纯文本,一行一个主机名,偶尔混着 IP、分隔符不一致、大小写不统一。直接拿去用,后面每一步都要重新处理一遍。
import re from collections import Counter def normalize(path, root): seen, rows = set(), [] with open(path, encoding="utf-8", errors="ignore") as f: for line in f: line = line.strip().lower() if not line or line.startswith("#"): continue # 只保留形如 xxx.root 的主机名,剔除裸 IP 和杂质行 host = re.sub(r"^[a-z]+://", "", line).split()[0].rstrip(".") if not host.endswith("." + root) and host != root: continue if host in seen: continue seen.add(host) rows.append(host) # 按层级深度排序,深层子域通常更值得先看 rows.sort(key=lambda h: (-h.count("."), h)) return rows hosts = normalize("result_raw.txt", "example.com") print("unique:", len(hosts)) print("depth dist:", Counter(h.count(".") for h in hosts))逻辑上就三步:清洗、过滤、排序。re.sub去掉可能残留的协议头,rstrip(".")处理末尾的点(DNS 里表示绝对域名,导出时经常带出来),endswith做归属校验,防止把不属于该根域的行混进来——被动收集的数据源经常返回别的域的结果。排序按点号数量倒序,是因为三级、四级子域(比如api.inner.example.com)往往比www这种更有信息量,先看它们能更快判断这份结果值不值得深挖。
输出里的depth dist是给自己看的体检指标:如果结果里 95% 都是两级,说明这次爆破基本只在浅层命中,要么字典太通用,要么并发参数把深层的慢响应吃掉了。
4. 泛解析与 CDN:结果里一半假数据是怎么来的
4.1 泛解析:为什么你导出三千条,两千九百条是假的
泛解析(wildcard DNS)是指域名配置了*.example.com指向同一个地址,任何不存在的随机标签都会被解析成功。对爆破工具来说这是灾难:字典里每一个词都会「命中」,结果集瞬间膨胀几千条,看起来收获巨大,实际全是同一个落地 IP。
判断方法不复杂:生成几个高熵随机标签去查,如果都能拿到 A 记录,就说明存在泛解析。
import random, string, subprocess def query(name, rtype="A", timeout=5): try: out = subprocess.run(["dig", "+short", rtype, name], capture_output=True, text=True, timeout=timeout).stdout except subprocess.TimeoutExpired: return [] return [l.strip() for l in out.splitlines() if l.strip()] def wildcard_ips(domain, probes=5, threshold=3): hits = {} for _ in range(probes): tag = "".join(random.choices(string.ascii_lowercase + string.digits, k=12)) for ip in query(f"{tag}.{domain}"): hits[ip] = hits.get(ip, 0) + 1 # 5 次随机探测中出现 >=3 次的 IP,判定为泛解析落地 return {ip: c for ip, c in hits.items() if c >= threshold} bad = wildcard_ips("example.com") print("wildcard ip:", bad)参数怎么定:probes至少 3 次,5 次比较稳;threshold取probes的 60% 左右。之所以不能「查到一次就算」,是因为部分泛解析配置会轮询返回不同的 IP,单次探测容易误判。拿到落地 IP 集合后,回到结果清单里把所有 A 记录落在这些 IP 上的主机剔除,这一步通常能砍掉 80% 以上的假阳性。
注意:有些泛解析返回的不是 A 记录而是 CNAME。只查 A 会漏判,实际排查时把 CNAME 一起查一遍,两边都空才算「真的不存在」。
4.2 CDN 与 IP 归属:别把边缘节点当成你的资产
剔除泛解析之后,剩下的大多是真子域,但它们的 A 记录可能指向 CDN 边缘节点。这意味着两件事:同一个子域在不同地区解析出不同 IP,以及你拿到的 IP 属于服务商而非目标,后续任何基于 IP 的判断都会跑偏。
识别思路是看解析结果的分布特征:同一子域多次查询返回多个不相关网段的 IP、CNAME 指向某个明显的加速域名、TTL 极短(几十秒),这三条占了两条基本可以判定走了 CDN。这时 IP 字段的正确用法是记录「解析指纹」而不是「资产地址」——把 CNAME 目标、IP 所属网段、解析时间一起留档,后面做变更比对时这些才是有效信号。
这一步不需要额外工具,dig多查几次就能看出规律:
# 同一子域连查 5 次,观察返回 IP 是否漂移、TTL 是否异常短 for i in 1 2 3 4 5; do dig +noall +answer api.example.com A | awk '{print $2, $5, $1}' sleep 1 done+noall +answer只打印答案段,awk取 TTL 和 IP。如果五次返回的 IP 分属不同网段、TTL 都在 60 以下,那基本就是 CDN。反过来五次完全一致、TTL 稳定在几百秒,才更可能是自有服务器。
4.3 二次复核:DNS 与 HTTP 两层筛子
被动收集的历史记录、爆破的即时结果,混在一起之后必须过筛。第一层是 DNS 复核,把 NXDOMAIN 的全部剔除——历史记录里挂掉的比例相当高。
# 批量复核候选清单,只留下仍能解析出 A 或 CNAME 的主机 while read -r h; do a=$(dig +short A "$h" 2>/dev/null) c=$(dig +short CNAME "$h" 2>/dev/null) if [ -n "$a$c" ]; then printf '%s\t%s\t%s\n' "$h" "${c:-none}" "$(echo "$a" | tr '\n' ',' )" fi done < candidates.txt > verified.tsv串行跑几千条会很慢,实际用xargs -P 20 -I{}包一层并发,20 路足够,再高又开始丢包。输出的 TSV 三列分别是主机名、CNAME 目标、A 记录列表,逗号分隔,方便后面拆。
第二层是 HTTP 探测,只对 DNS 复核通过的主机做,取状态码和Server头。这一层的价值不在「能不能访问」,而在于区分「解析存在但服务已下线」和「真正在跑服务的资产」——前者留着占位,后者才是下一步要看的。
5. 避坑记录:五个把我坑到重跑的现场
5.1 启动与运行阶段的三条
现象:双击后窗口一闪而过,或者进度条走完结果为空。原因几乎都是路径问题:压缩包在中文目录下解压、目录名带空格、字典文件没跟主程序在一起,程序读不到词表就直接结束,不报错。 解决:换到纯英文无空格路径重新解压,手动确认字典文件的存在与可读性;如果支持在界面里指定字典路径,优先用绝对路径。
现象:跑到一半程序卡死或者自动退出,进度停在某个百分比。原因是并发和本地网络不匹配导致大量请求堆积,或者中途切换了网络(拔网线、切 WiFi)让连接状态全乱。 解决:把线程降到 30 先重跑一遍确认能跑完,再逐步加;长任务中途不要切网络,跑之前把休眠和自动断网关掉。
现象:导出的文件里中文乱码、每行末尾带奇怪字符。原因是导出编码和编辑器打开时的编码不一致,从网页复制来的字典也常带不可见字符。 解决:统一用 UTF-8 导出和打开,字典先过一遍第 3.1 节的清洗管道;打开时明确指定编码,别让编辑器猜。
5.2 结果判读阶段的两条
现象:命中数量惊人,几千条结果,但打开一看几乎全在一个 IP 上。原因是目标配了泛解析,工具把每个字典词都判成了存在。 解决:先做第 4.1 节的随机标签探测,拿到落地 IP 集合后批量剔除;同时把工具里的泛解析过滤打开,双保险。
现象:明明知道某个子域存在,跑完清单里就是没有。原因通常是三个之一:字典里没有这个词;并发过高把慢响应吃成了不存在;DNS 服务器对该域名的解析结果本身就有问题。 解决:按顺序排查——先把该主机名手工写进字典跑一次单条,能出结果说明是召回问题,回第 3.2 节做自检;不能出结果就换一个 DNS 服务器再试;两边都不行,用dig +trace跟一遍解析链路看是哪一跳断了。
6. 从一次枚举到可复跑资产表:差异比对与字段设计
单次枚举的输出只是一份名单,价值有限。真正有用的是把它变成一张能反复跑、能对比的表——因为子域名的变化本身就是信号,新冒出来的一个test前缀,往往比存量的一千条更值得看。
先说字段设计。我一般固定七列:host(主机名)、root(所属根域)、cname(CNAME 目标,无则填 none)、ips(A 记录,逗号分隔)、source(被动 / 爆破 / 两者)、first_seen、last_seen。source这一列别省,它能直接告诉你这条结果的置信度——两条腿都命中的,基本可以确定是真资产。
| 字段 | 类型 | 用途 |
|---|---|---|
| host | 文本 | 主键,去重依据 |
| cname | 文本 | 判断是否走 CDN 的关键 |
| ips | 文本 | 变化即意味着后端迁移 |
| source | 枚举 | 被动 / 爆破 / 两者都有 |
| first_seen | 日期 | 新增资产的判定依据 |
| last_seen | 日期 | 清理下线资产的依据 |
然后是差异比对,这一步用comm就够了,前提是两个文件都排好序:
# 两次枚举的结果做双向差集,新增的才是本周真正要看的 sort -u run_week1.txt > a.txt sort -u run_week2.txt > b.txt comm -13 a.txt b.txt > new_hosts.txt # 只在 week2 出现:新增 comm -23 a.txt b.txt > gone_hosts.txt # 只在 week1 出现:疑似下线 wc -l new_hosts.txt gone_hosts.txtcomm要求输入严格有序,所以两个sort -u不能省。-13的含义是「抑制第 1 列和第 3 列,只输出第 2 列」,也就是只出现在第二个文件里的行。这个写法比grep -vxF快得多,数据量上到十万行也不卡。
new_hosts.txt里的每一条都值得单独看一眼:是业务真的新上线了服务,还是有人临时起了个测试环境忘了关。后者在自查场景里出现的频率高得离谱,我在不止一次梳理中发现过挂着默认页面的临时子域,跑完顺手清掉,比事后补救省事得多。
最后一个习惯,是每次跑完把「输入 + 参数 + 结果 + 哈希」打成一个包留档。我吃过亏:两周后看到一份清单,想不起来当时用的是哪个字典、线程开了多少,只好整批重跑一遍,浪费的时间比跑一次多三倍。从那以后我每次跑完都强制走一遍这个归档流程,文件名里带上字典版本和线程数,宁可多占几百兆,也不留一个说不清来源的结果。希望帮到你。
本文还有配套的精品资源,点击获取