从 85% 假阳性到 0:子域名枚举踩坑实录(个人开源工具)

📅 2026/8/13 9:49:06
从 85% 假阳性到 0:子域名枚举踩坑实录(个人开源工具)
一次真实项目里 580 个子域名结果 85% 是假的排查到最后发现是一个判断条件写错了。这篇文章记录我自研子域名枚举工具的完整踩坑过程以及最终沉淀下来的工程化方案。---起因580 个结果85% 是假的做资产测绘时对某集团域名跑了一轮子域名枚举工具返回580 个存活子域名。按正常流程这批结果该进入端口扫描环节了。但直觉告诉我不对——这个域名不大580 个存活不太合理。抽样验证结果很惨580 个里 85% 是假阳性。所谓存活其实根本没解析出任何 IP。第一个坑grep -qv ^$ 判存活排查代码找到了罪魁祸首# 错误写法非空输出就算存活 ip$(dig short $host) [ -n $ip ] echo $host # 或 grep -qv ^$ 判断 # 正确写法必须解析出合法 IPv4 才算存活 ip$(dig tcp short $host | grep -E ^([0-9]{1,3}\.){3}[0-9]{1,3}$ | head -1) [ -n $ip ] echo $host区别在哪dig short 对不存在的域名可能返回CNAME 记录或NOERROR 空应答——输出非空但没有 A 记录。用非空即存活判断这些全被当成存活。这个错误有多致命580 个假阳性意味着端口扫描白跑对不存在的域名扫端口HTTP 探测全超时白白等人工复核消耗大量时间**最关键的真实资产淹没在噪声里可能被漏掉**第二个坑泛解析Wildcard DNS泛解析是枚举的另一个大敌。某些云厂商/IDC 配置了 *.domain.com → IP任何随机子域都能解析出 IP枚举结果全部存活但全是同一个 IP。检测方法随机生成 3 个子域名wc 随机串只要能解析出 IP就判定为泛解析for _ in 1 2 3; do randwc$(head -c 8 /dev/urandom | md5sum | cut -c1-8) wip$(dig tcp short $rand.$DOMAIN $RESOLVER 2/dev/null | tail -1) [ -n $wip ] { echo 泛解析: $rand.$DOMAIN - $wip; break; } done检测到泛解析后对枚举结果逐一比对 IP等于泛解析 IP 的全部剔除。但注意同一泛解析 IP 上可能跑着多个真实虚拟主机带不同 Host 头有真实站点。所以泛解析 IP 上的 403 不能直接当假阳性——用不同 Host 头测301/302 跳转 有真实站点。这个我们实测踩过三门峡医院案例。第三个坑UDP 53 出站被墙在 Kali VM 环境UDP 53 出站被防火墙限制。dnsx、massdns 默认走 UDP 查询结果随机丢失——同一域名查 3 次3 次结果完全不同。这比假阳性更隐蔽不是全错是随机错你根本不知道哪次结果可信。解法所有验证统一走 dig tcpTCP 53稳定可靠。虽然比 UDP 慢一点但结果可信。第四个坑裸域漏验误报0 存活有段时间枚举某域名返回0 存活但手动 dig 主域名明明有 A 记录。排查发现subfinder 只返回子域名裸域主域不会出现在输出里。如果主域有 A 记录但没配任何子域解析不把种子本身加进验证列表整个域名就被误判0 存活。修复很简单第一轮被动收集后种子域名本身必须加入验证候选NEW$NEW $s $(subfinder -d $s -all -silent 2/dev/null) # ^^^^ 种子本身第五个坑断点续跑被旧数据污染支持断点续跑后出现了一个诡异 bug结果数量≈字典大小5000 行。排查发现是旧版本残留的 all_subs.txt 污染——里面存的是未严格验活的候选续跑时对这 5000 个假种子全量套娃。修复重跑前确认 all_subs.txt 行数正常远小于字典大小或者删掉目录全新开跑。断点续跑保留历史结果没问题但前提是历史结果本身是干净的。第六个坑候选爆炸8 个种子 × 5000 词字典 40000 个候选10 并发 digtcp跑几个小时只出 1 个存活。修复只对主域一级爆破不对二级子域再套娃。第 2 轮套娃只对高价值种子dms/tms/crm 这类小字典。最终方案两阶段工程化把所有坑踩完沉淀出两阶段方案阶段一预筛 prescreen.sh 裸域 A 记录 被动源命中数 泛解析检测 → 判定废弃 | 半死 | 有效 | 泛解析 → 避免对无资产域名空跑数小时 阶段二多轮枚举 multi_round_enum_v5.sh 第 1 轮subfinder 被动收集CT日志/公开DNS/搜索引擎不碰目标 第 2 轮高频词 Top100 快筛 → 已发现子域 × 字典深挖 每一轮dig tcp 严格 A 记录验证 双 resolver 重试 泛解析 IP 过滤 → 断点续跑 → 汇总存活域名IPHTTP状态核心原则1.被动打底 → 主动补深第 1 轮 OSINT 不碰目标挖出 idp-eagleyun、zhaopin 这类字典里没有的内部专属名第 2 轮爆破补漏通用名2.深度优先于广度挖到 12 个高质量资产 挖 500 个假阳性3.结果可复核每一步留中间文件任何判定都能回溯效果假阳性率**85% → 0**严格 IPv4 验证 泛解析过滤 双 resolver预筛阶段直接废弃无资产域名节省数小时空跑输出域名 IP HTTP 状态可直接进入端口扫描环节---代码开源[github.com/WzzZh-zh/subdomain-enum-toolkit](https://github.com/WzzZh-zh/subdomain-enum-toolkit)经验一句话枚举工具的 bug 不会让工具崩溃它会让结果看起来对——而看起来对比报错可怕一万倍。资产收集的核心不是跑得快而是结果可信。