资讯动态

域名劫持工具:DNS解析链路四模块验证与隔离靶场实践

发布时间:2026/10/24 4:37:54 来源:尧图企业网站定制
简介面向网络管理员、安全测试人员及运维学习者的DNS劫持测试工具包主要用于在受控环境中模拟域名重定向验证DNS配置正确性与网络安全性。压缩包为RAR格式体积约227KB上游文件总数标记为0、类型明细暂无数据包内实际构成需以解压结果为准。工具围绕自定义DNS解析展开可将指定域名指向目标IP并配合重定向活动记录、按用户或网段实施安全控制、临时或永久重定向等能力便于开展内部流量管理、故障排查及网站迁移时的解析验证也可用于识别钓鱼重定向等风险。已有2245人学习或下载适合需要理解域名劫持原理、检查解析风险并做小规模测试的读者参考。使用时应在授权环境内操作测试后及时恢复DNS配置避免影响正常服务。1. 域名劫持工具到底在劫什么从一次解析跑偏说起前阵子有位同行遇到一件棘手的事内网管理后台的地址栏没变、图标没变、证书锁也还在输完账号密码才发现登录页是假的。事后检查发现服务器没有任何改动真正被改动的是“域名变成 IP”这一步。域名劫持工具就是围绕这一步做的验证工具集包含 hosts 改写、伪造应答、缓存投毒模拟、解析记录比对四个模块四个模块分别对应一层可信度每次动作都会留下日志。它要解决的是“我这条链路有多容易被改写、被改写之后多久能发现”这个问题并非“怎么攻破别人”。做安全测试、DNS 运维、应急响应的读者可以在隔离环境里跑通一遍新手照着步骤也能复现。2. 把解析链路拆成四段域名劫持工具的四个动手位置2.1 一次递归解析的五跳每跳的可信度都不一样从浏览器敲下回车到拿到一个 A 记录中间至少经过五跳应用的 stub resolver、操作系统的解析库与缓存、本机配置的递归解析器、根与顶级与权威这一串委派、最后是应答原路返回。这五跳里只有权威服务器那一跳是“域名持有人说了算”的其余四跳都在别人的地盘上。工具之所以有价值是因为它把每一跳单独拎出来让你看到“这一段断了会怎样”。真正让劫持变得可行的是 DNS 协议本身的设计。UDP 53 端口上的查询和应答没有任何认证机制客户端判断“这个应答是不是我要的”靠的只有三样东西查询报文里的 16 位 TXID、UDP 源端口、以及查询的名字本身。攻击者要做的就是在真正的权威应答到达之前抢先塞一个伪造应答进去并且让 TXID 和源端口对得上。这就是常说的抢答比拼的核心是时间窗口的大小。历史上把这个窗口压得最狠的一次改进是递归解析器开始对源端口做随机化。在这之前很多实现会把源端口固定或者顺序递增攻击者只要猜中 16 位 TXID 就有机会。源端口也加入随机化之后两条 16 位合起来大约 32 位熵配合“先制造一次缓存未命中、再抢在权威之前应答”的手法命中率被压到很低但并非为零。工具的缓存投毒模拟模块测的就是这个“很低”到底有多低。再往上一层是委派。域名的 NS 记录指向哪台权威、DS 记录有没有配上、注册商那边的解析服务器设置有没有锁这些属于“域名持有人自己这边的门”。工具里对应的模块做的其实是比对把多个出口、多个时间点的解析结果拉到一起找出不一致的那一条。这部分在应急场景里比前面三个模块都实用因为它不需要你动手只需要你看得懂差异。2.2 四个攻击面从客户端一路往上到注册商把上面几跳摊开能动手的位置其实就四类。工具把每一类做成了一个模块模块之间互不依赖可以单跑也可以串起来跑。攻击面典型手法工具模块影响范围复现难度客户端本地改 hosts、改 DNS 设置、装带解析能力的扩展hosts 改写单机极低局域网链路同网段伪造应答、抢占网关伪造应答一个广播域中递归解析器制造缓存未命中后抢答、控制转发器缓存投毒模拟该递归下的所有客户端高注册与权威侧改 NS、改注册商设置、区域传送泄露记录比对整个域名取决于账号安全这张表最该看的是最后一列。复现难度越低的攻击面现实里出现的概率反而越高因为它不需要什么技术只需要拿到一台机器或者一次社工。所以做排查的时候顺序应该反过来先查最低成本的那一层再往上走。2.3 为什么工具要拆成四个模块而不要合成一个我一开始也觉得拆成四个是多余的直到在一个靶场里连续踩了两次坑才明白。四个模块的前置条件完全不同hosts 模块只改本地文件不需要任何网络权限伪造应答模块要求工具所在的位置和目标客户端在同一个二层广播域跨了三层就没戏缓存投毒模拟模块要求你能直接向某个递归解析器发查询也就是你得在它的允许递归列表里记录比对模块压根不需要发包它只需要几个能用的解析出口。如果做成一个“一键劫持”的按钮你会分不清失败到底是“手法不对”还是“环境不对”。分开之后每一步的成败都能归因。这也是我在写实验脚本时坚持的一条每个模块单独跑通、单独留日志再去串。2.4 先用两条命令量化随机化强度在动手之前先花五分钟看看你所在环境的解析器“抗不抗抢”。抓一段解析器出口的查询把 TXID 和源端口导出来看分布。# 在解析器出口抓 40 个查询报文只看 UDP 53 sudo ip netns exec lab-resolver \ tcpdump -i veth-r2 -n -c 40 udp port 53 -w /tmp/lab53.pcap # 导出 TXID 和源端口统计每个源端口出现了几次 tshark -r /tmp/lab53.pcap -T fields -e dns.id -e udp.srcport 2/dev/null \ | sort | uniq -c | sort -rn | head-c 40控制抓包数量40 个足够看出趋势-e dns.id取 DNS 头里的 16 位标识-e udp.srcport取源端口。判读方式很直接如果 40 个包里源端口只有两三个值或者 TXID 呈等差递增说明随机化很弱后面抢答的命中率会明显上升如果源端口高位分散、TXID 看不出规律说明这个出口的防护在线工具跑出来的“失败”是真实失败不要反复调参数去凑。提示这一步只抓自己实验网段里的报文。生产环境要做同类观测走镜像口或者解析器自身的查询日志不要在共享网段上抓包。接下来才是搭靶场。没有隔离环境后面所有结论都是玄学。3. 隔离靶场怎么搭三个命名空间和一条只跑 .lab 的解析链3.1 地址规划与三条硬性要求靶场不追求像生产追求的是“每次跑出来结果一样”。我一般用三个 network namespace 拼一条最小链路客户端、递归解析器、自建权威。三者用 veth 两两对接地址全部落在 10.10.0.0/16 里不碰默认路由不碰宿主机解析配置。角色命名空间接口地址作用被测端lab-clientveth-c10.10.10.20/24发起查询观察是否跑偏递归lab-resolverveth-r1 / veth-r210.10.10.1 / 10.10.20.1工具的主要落脚点权威lab-authveth-a10.10.20.53/24提供真实应答作为对照三条硬性要求域名一律用.lab这类保留后缀永远不要拿真实在跑的域名做实验递归只服务实验域其余查询一律拒绝避免靶场变成开放解析器每轮实验前回滚快照或者重建命名空间不要指望“清一下缓存就好了”。3.2 建命名空间并把 veth 对接起来# 三个隔离命名空间 sudo ip netns add lab-client sudo ip netns add lab-resolver sudo ip netns add lab-auth # client - resolver sudo ip link add veth-c type veth peer name veth-r1 sudo ip link set veth-c netns lab-client sudo link set veth-r1 netns lab-resolver 2/dev/null || sudo ip link set veth-r1 netns lab-resolver sudo ip netns exec lab-client ip addr add 10.10.10.20/24 dev veth-c sudo ip netns exec lab-resolver ip addr add 10.10.10.1/24 dev veth-r1 sudo ip netns exec lab-client ip link set veth-c up sudo ip netns exec lab-resolver ip link set veth-r1 up sudo ip netns exec lab-client ip link set lo up sudo ip netns exec lab-resolver ip link set lo up # resolver - auth sudo ip link add veth-r2 type veth peer name veth-a sudo ip link set veth-r2 netns lab-resolver sudo ip link set veth-a netns lab-auth sudo ip netns exec lab-resolver ip addr add 10.10.20.1/24 dev veth-r2 sudo ip netns exec lab-auth ip addr add 10.10.20.53/24 dev veth-a sudo ip netns exec lab-resolver ip link set veth-r2 up sudo ip netns exec lab-auth ip link set veth-a up sudo ip netns exec lab-auth ip link set lo up # 客户端默认路由指向递归否则出了命名空间就不知道往哪走 sudo ip netns exec lab-client ip route add default via 10.10.10.1 # 关键一步resolv.conf 要写进 /etc/netns/name/ 下别直接 echo 覆盖 sudo mkdir -p /etc/netns/lab-client echo nameserver 10.10.10.1 | sudo tee /etc/netns/lab-client/resolv.conf这段脚本里唯一需要解释的是最后两行。ip netns exec会把/etc/netns/名字/下的文件 bind-mount 到命名空间里的对应路径所以写resolv.conf必须写在这个目录下。如果你图省事直接ip netns exec lab-client sh -c echo ... /etc/resolv.conf改的是宿主机的解析配置——这个坑我在第 5 章还会再提一次因为它真的很容易中招。ip route add default via 10.10.10.1这一行决定了客户端的所有查询都会打到递归上递归那边用什么上游、走不走转发跟客户端无关这样实验变量就收窄到一个点上。3.3 起一个只服务实验域的递归递归用 dnsmasq 就够了配置短、日志清楚、行为可预期。核心是no-resolv加上一条指向自建权威的server其余域名一律不转发。# /etc/dnsmasq-lab.conf port53 bind-interfaces listen-address10.10.10.1 # 不读 /etc/resolv.conf避免把宿主机的上游带进来 no-resolv no-hosts no-poll cache-size1000 # 只有实验域交给自建权威其他域名没有匹配的 server 就会被拒绝 server/demo.lab/10.10.20.53 # 先关掉 DNSSEC 校验第 6 章要用它做对照 dnssecoff log-queries log-facility/var/log/dnsmasq-lab.logno-resolv是关键没有它dnsmasq 会把宿主机的上游服务器全读进来你的实验域查不到就会去公网绕一圈日志里全是噪音。listen-address只绑客户端那一侧的地址递归不会在权威网段上暴露。log-queries一定要开后面判读模块输出、对齐时间戳全靠它。# 起递归 sudo ip netns exec lab-resolver \ dnsmasq -C /etc/dnsmasq-lab.conf --pid-file/run/dnsmasq-lab.pid # 看它是不是真的起来了以及绑在哪个地址上 sudo ip netns exec lab-resolver ss -lunp | grep :53ss -lunp | grep :53输出的本地地址应该只有10.10.10.1:53。如果看到0.0.0.0:53或者[::]:53说明bind-interfaces没生效检查配置文件里有没有多打一个空格或者顺序放错。3.4 基线验证三次查询看 TTL 和来源靶场起好之后先别急着上工具。跑三次查询把“没被动过手脚的样子”记下来后面所有异常都是相对这个基线说的。# 第一次看应答段、权威段和统计信息 sudo ip netns exec lab-client \ dig 10.10.10.1 www.demo.lab A noall answer authority stats # 隔 5 秒再查TTL 应该递减说明走的是递归上的缓存而不是每次回源 sleep 5 sudo ip netns exec lab-client dig 10.10.10.1 www.demo.lab A noall answer # 确认客户端真的在用这个出口 sudo ip netns exec lab-client cat /etc/resolv.confnoall answer只显示应答记录干净authority会带上权威段能看出应答是不是来自你自建的权威stats里有个Query time第一次通常比第二次大因为第二次命中缓存。如果第二次的 TTL 没变说明缓存没生效先回去看cache-size和查询名是否完全一致——大小写不同在 DNS 里算同一个名字但有的实现会当成两次查询。注意基线这一步不要跳过。第 4 章所有“是否被改写”的判断比的都是这里记录下来的答案、TTL 和应答来源。4. 四个模块的参数与判读dry-run、TTL 和抢答窗口怎么设4.1 hosts 层最容易复现也最容易还原hosts 模块看着最不起眼实际是排查时最先要排除的一层。它只改本地文件不涉及任何网络行为所以复现成本最低也最容易被误判成“网络问题”。# 改之前先备份实验结束后第一件事就是还原 sudo cp -a /etc/hosts /etc/hosts.lab.bak printf 10.10.10.99\twww.demo.lab\n | sudo tee -a /etc/hosts # getent 走的是系统解析顺序能直接看出 hosts 有没有压过 DNS getent hosts www.demo.lab sudo ip netns exec lab-client getent hosts www.demo.lab # 还原 sudo mv /etc/hosts.lab.bak /etc/hostsgetent hosts比dig更贴近应用的真实行为因为应用大多走getaddrinfo而dig是直接向指定服务器发查询会绕过 hosts。这两个命令的差异本身就是一个重要信号如果getent的结果和dig对不上差异大概率出在 hosts、nsswitch 顺序或者本地 stub 缓存上跟链路层无关。这里有个必须提前知道的点/etc/hosts不属于 network namespace你在命名空间里改和在外面改是同一个文件。所以第 3 章把resolv.conf挪到/etc/netns/下的做法对 hosts 不适用。做干净实验的方式是改完之后立刻还原不要指望命名空间帮你隔离。4.2 伪造应答模块先 dry-run再谈抢答这个模块的前置条件最苛刻——工具必须能看到客户端的查询并且能在同一广播域内把应答送回去。参数不多但每一个都会显著改变结果。参数作用实验建议值说明--iface收发报文的接口veth-r1必须是能双向看到查询的那个口--rule域名匹配规则*.demo.lab支持通配写太宽会误伤同名后缀--answer返回的伪造 A 记录10.10.10.99指向你自己的观测机别指向真实服务--ttl应答里的 TTL60越短越像攻击者越长污染窗口越大--delay应答延迟单位毫秒0用来控制抢在真实应答之前的时机--mode应答模式racerace只抢第一次always每次都应答--log日志路径/tmp/lab.log后面判读全靠它第一次一定用--dry-run。它只做规则匹配和日志预演一个报文都不发你能从输出里确认命中的 qname 到底有哪些。# 只预演确认规则命中的只有你自己的测试域名 sudo ip netns exec lab-resolver \ ./domain-redirect --iface veth-r1 \ --rule *.demo.lab \ --answer 10.10.10.99 \ --ttl 60 \ --delay 0 \ --mode race \ --log /tmp/lab.log \ --dry-run--rule *.demo.lab里的通配要写全后缀。写成*demo.lab会把notdemo.lab也匹配进来这种边界问题在 dry-run 阶段最容易发现跑起来之后就很难注意到了。--ttl 60是个折中值设成 0 每次都要重新抢实验次数一多就很累设成 3600 又会让客户端缓存太久下一轮实验前必须手动清。去掉--dry-run之前先确认两件事规则命中的 qname 全在你自己的实验域里--answer指向的地址是你自己的观测机不是任何真实在跑的服务。这两条任何一条不满足就停下来改参数。4.3 缓存投毒模拟TTL 是核心变量这个模块测的是递归解析器的抗抢答能力参数比上一个多但真正影响成败的就三个随机子域前缀的生成方式、并发查询数、以及抢答延迟的分布。原理不复杂先让解析器对一个从没查过的名字产生缓存未命中它在回源的过程中会向外发查询你在这个窗口里抢先应答。名字每次都必须不一样否则第二次就命中缓存了根本不会回源。所以工具会按规则生成一串随机前缀比如a7f3.demo.lab、9c21.demo.lab每个只用一次。并发数决定你能覆盖多大的窗口。单线程串行发查询窗口很窄命中率低开到 50 到 100 并发能明显感觉到差异但再往上会被解析器的限速和丢包吃掉日志里会出现大量超时看起来像失败其实是打满了。我一般从 20 起步每次翻倍直到命中率不再上升为止那个拐点就是这个环境的上限。延迟分布比延迟本身重要。--delay 0意味着应答和查询几乎同时发出能不能抢到全看网络栈的调度把延迟做成 0 到 5 毫秒的随机分布命中率反而更高因为它覆盖了不同的回源路径耗时。工具的日志里会记录每次的 TXID、发出时刻、应答时刻和判定结果判读的时候看的是“抢到的那批的延迟落在哪个区间”而不是平均值。提示如果实验目标是评估 DNSSEC 的效果把第 3 章配置里的dnssecoff改成dnssecon再跑同一组参数。你会看到抢答照样成功但客户端会因为签名校验失败而丢弃应答——这正是 DNSSEC 的价值所在它不阻止你抢它阻止你被相信。4.4 日志判读与报告导出四个模块的日志格式我统一成了七个字段时间戳、规则、qname、qtype、answer、抢答耗时、判定。判定只有win、miss、timeout三种不要加模糊状态加了之后统计就没法做了。# 按规则统计命中次数、抢到次数和平均耗时 awk {hit[$2]; sum[$2]$6; if($7win) win[$2]} END{for(k in hit) printf %-16s 命中%d 抢到%d 均值%.2fms\n, k, hit[k], win[k], sum[k]/hit[k]} \ /tmp/lab.log$2是规则名$6是抢答耗时$7是判定。这段统计能一眼看出三件事规则是不是写宽了命中数远超预期、抢答是不是真的成功抢到数占比、耗时是不是异常均值突然变大通常意味着环境里有别的东西在抢带宽。报告导出走的是同一份日志只是按 qname 分组再拼上当时的dig结果做交叉。只有日志和dig结果同时指向“答案被改成了 10.10.10.99”这一条才算复现成功。单看日志不算单看页面跳转更不算这个原则在第 5 章会反复用到。5. 避坑记录五类翻车现场与固定排查顺序5.1 先查这三处再怀疑工具遇到“结果不对”的时候我的固定顺序是第一步确认客户端到底走哪个解析出口第二步确认报文有没有真的到达工具所在的接口第三步才去看应答有没有被客户端接受。这三步能挡掉八成的误判因为绝大多数“工具失效”其实是环境没配对。顺序不能反。先去看工具参数很容易越调越乱最后连基线是什么样都忘了。第 3 章记录下来的那次dig输出就是这时候用来对照的。5.2 五条现象、原因和解决第一条在命名空间里改了resolv.conf回到宿主机发现宿主机的解析也变了。现象是宿主机突然解析不了内网域名或者cat /etc/resolv.conf里出现了10.10.10.1。原因是ip netns exec只隔离网络栈不隔离挂载点/etc/resolv.conf是同一个文件你echo进去就是改的宿主机。解决方式是写/etc/netns/lab-client/resolv.confip netns exec会自动把它 bind-mount 进去。已经被改坏的重新生成一份或者从备份恢复。第二条工具日志显示“已应答 N 次”客户端解析结果一点没变。现象很迷惑日志一片成功dig出来的还是原始地址。原因通常有三个客户端走了 DoH 或 DoT直接绕过了本地 53 端口系统里跑着 stub resolver缓存没清或者 nsswitch 里 hosts 排在 dns 前面而 hosts 里有残留条目。排查方式是先用dig 10.10.10.1直连递归做对照——直连能改、getent不能改问题就在客户端本地这一层。解决方式是关掉浏览器的安全 DNS、清掉本地缓存、检查 hosts 有没有历史残留。第三条第一轮能复现第二轮怎么都不行。现象是同样的参数、同样的拓扑第二轮命中率掉到零。原因是缓存递归上的缓存、客户端的缓存、连接跟踪表里的会话三层任意一层有残留都会让第二轮“看起来失效”。解决方式是在实验脚本开头固定加三步重启递归进程、重建命名空间、清客户端缓存。别指望“等一会儿就好了”等待时间不确定实验就不可复现。第四条同一个域名两次查出来不一样的地址在几个值之间来回跳。现象是dig三次拿到三个不同 IP看起来像被劫持了。原因是上游本来就是多线或者有内容分发的权威返回不同地址是正常行为另一个常见原因是客户端优先走 IPv6而你只观察了 A 记录。解决方式是固定qtypeA、用noall answer只看应答段客户端侧临时关掉 IPv6sysctl -w net.ipv6.conf.veth-c.disable_ipv61再对比。判断“是否被改写”一定要基于同一 qtype、同一出口、同一时刻的三元组。第五条报告里写“劫持成功”人工复核发现是假阳性。现象是页面确实跳到了另一个登录页但解析记录完全正常。原因是把“应用层跳转”当成了“解析被改写”——重定向、反向代理、前端路由都能做出一样的效果。解决方式是把判定标准收紧成三条同时成立dig的应答地址变了、TLS 证书的签发对象对不上、TCP 连接的真实目的 IP 和dig结果一致。三条里任何一条对不上就不算复现成功老老实实写“未复现”。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑