SAD DNS漏洞解析:ICMP侧信道如何击穿DNS源端口随机化
1. 先弄清楚SAD DNS 到底是什么问题1.1 DNS 投毒这个老话题为什么还能有新文章做网络运维越久越会意识到一件事DNS 是整个互联网里最信任人、也最容易被欺负的那个角色。它的核心机制就是缓存谁先回答就信谁在没有 DNSSEC 的年代几乎等于把家门钥匙挂在门口。过去我们谈 DNS 投毒说的是攻击者伪装成权威服务器抢在真实响应之前塞进一条“假记录”一旦递归服务器缓存了这条假记录用户访问的就是攻击者指定的服务器轻则被导向钓鱼网站重则业务链路被整体劫持。防御这边也不是没有努力。早期递归服务器只校验 16 位的 DNS 事务 ID后来大家发现这个随机空间太小又加上源端口随机化让攻击者需要同时猜中“事务 ID 源端口”组合空间从 2 的 16 次方跳到了 2 的 32 次方。这个思路相当长一段时间内被看作足够安全的默认配置。CVE-2020-25705SAD DNSSide channel Attacked DNS之所以让安全圈震动不是因为它发明了一种全新的攻击手法而是它提供了一条旁路把“预测源端口”这件事从蛮力猜测变成了可观测的推理过程。这就好比你原本要蒙着眼睛去猜别人手里拿的是哪张牌现在有人偷偷告诉你牌背面有一道划痕可以摸到你还觉得这游戏公平吗我第一次读到相关论文时最大的冲击不是攻击链路有多复杂而是它背后的思路非常朴素攻击者不需要看到递归服务器发出的真实查询包只需要利用系统在网络层暴露出的行为就能推断出随机源端口。看完之后我最大的感想是我们对“随机化”三个字的信任很多时候建立在“它看起来随机”上但一旦有了可观测的侧信道再好的随机也是白搭。1.2 一个 CVE 编号背后的攻击坐标CVE-2020-25705 聚焦在 Linux 内核处理 ICMP 错误信息时暴露出的行为差异。要理解这个漏洞得先把 DNS 查询的路径画在脑子里。客户端把递归服务器当作“第一站”递归服务器再向各级权威服务器发起迭代查询这时候递归服务器会临时开一个 UDP 套接字系统内核为这个套接字分配一个高位源端口然后在这个源端口上等待权威服务器的响应。攻击者想让权威服务器返回的“抢答包”被递归服务器接受就必须使用同样的目标 IP、目标端口、源 IP、源端口、协议号以及一个匹配的 16 位事务 ID。如果源端口是真实随机的攻击者面对的是一个约 2 的 32 次方的搜索空间在公网上用普通流量去撞成功率低得可怜。SAD DNS 的思路就是把“源端口”这个维度从猜测空间里拿掉变成可探测信息。攻击者向解析器所在主机发送大量 UDP 探测包根据回程的 ICMP 端口不可达消息判断哪些端口当前未被占用从而摸清递归服务器那张 UDP 套接字表的“空闲地图”。一旦攻击者知道某个端口此刻是空闲的再去猜事务 ID唯一的随机性只剩 16 位生日攻击下的成功率就变得非常可观。这里有个容易被误会的细节SAD DNS 不是直接利用某一条“漏洞代码”一步拿下解析器。它更像是一个组合拳把 Linux 内核的 ICMP 应答行为当成了信息源把源端口从“隐私数据”变成了“侧信道数据”。旧防线里“源端口随机化”带来的安全性并不是被暴力破解而是被一个我们从未设想的回程错误包给绕过了。2. 攻击原理拆解从“盲猜”到“半盲”2.1 老防线是怎么建的事务 ID 与源端口先回到最基础的问题为什么早年 DNS 投毒容易成功早期递归服务器向外发出查询时使用固定的源端口攻击者只需要猜对一个 16 位事务 ID。16 位是 65536 种可能如果是广撒网式投毒发送几千上万条伪造响应碰中的概率并不低。后来主流实现增加了源端口随机化攻击者需要知道查询包的“五元组”中两个动态部分源端口和事务 ID。两者合在一起可能性大约有 40 亿种组合之前很多运维认为这就是铜墙铁壁。但“随机”本身要看随机数质量、内核端口分配策略、以及端口分配结果是否会被外部观测。我见过一些设备名义上启用了端口随机化实际实现却只是从一个很小的区间里轮询比如每次加 1 循环这样的随机形同虚设。BIND 曾经允许管理员配置出口端口范围如果配置不当把范围限定得特别小再来个生日攻击就很容易产生碰撞。真正的安全边界是动态参数必须无法被预测且不会以任何方式外泄。CVE-2020-25705 打破的正是后者。2.2 用 ICMP 错误包当“温度计”这是 SAD DNS 最核心的创意。想象你站在一栋楼外面楼里某个房间的灯是随机亮的你想知道下一次哪盏灯会亮但你既看不到楼内也听不到任何广播。这时候你改变思路你不去看灯而是去敲那些你看不到的房间门如果某个房间没有住人门会自己弹回来告诉你“这里无人”。UDP 和 ICMP 的关系就是如此向主机的某个未监听端口发 UDP 包主机通常会用 ICMP 端口不可达来回应。如果端口是有程序监听的可能没有回应或者回应方式不同。攻击者把解析器主机所有可能的端口分成两池已占用和未占用。它不需要知道已占用端口上跑的是什么服务只需要基于回程 ICMP 辨别出哪些端口目前是空的。递归服务器发起查询时系统会为这个查询临时占用一个高位端口查询结束后该端口就会释放并标记为空闲。如果攻击者能高频率地探测端口占用状态就能在递归服务器发起下一次查询时大概率知道它将从“哪些空闲端口”里选一个。这里的关键不是精确预测下一次用哪个端口而是把端口范围的可选集合从“整个 16 位空间”缩短到“当前一刻的空闲子集”。配合生日攻击碰撞成功的机会大大增加。2.3 现实环境里这事到底成不成立任何攻击设计都要考虑现实条件。SAD DNS 成功与否有几个前置因素第一递归服务器所在主机的内核是否存在可被利用的 ICMP 行为差异第二攻击者与被攻击解析器之间的网络路径允许 UDP 探测包到达主机并且允许回程 ICMP 包回到攻击者第三解析器当时确实发起了新的查询而且攻击者的探测频率必须跟得上查询频率。公网环境下防火墙可能屏蔽 ICMP运营商也可能限速 ICMP很多内网场景里主机位于 NAT 后面回程路径就不一定通了。这也是为什么很多研究者说SAD DNS 的理论危害很大实际复现的成功率高度依赖部署环境。再补一个容易被忽略的点解析器的“并发查询端口池”也不是越小越好。端口池越大空闲端口越多攻击者探测工作量越大但可用随机性却不一定相应提升端口池太小查询排队严重又容易引发性能问题。SAD DNS 的论文里提到了不同解析器的端口分配策略差异有的解析器倾向于顺序分配有的是随机分配有的取模分配。运维人员升级补丁时如果不看自家解析器实际的端口分配算法可能只是把补丁打了但问题根源没变。3. 自查视角我怎么判断自己的解析器是否暴露在风险中3.1 先看内核与软件补丁再看可能的探测量级如果你负责一台递归解析服务器看到 CVE-2020-25705 的第一反应应该是确认版本状态而不是立刻把配置翻个底朝天。漏洞影响的范围主要是 Linux 内核以及部分依赖内核 UDP 套接字行为的系统组件。某个服务跑在容器里但容器底下宿主机内核有漏洞依然可能被当作探测目标。所以自查的第一步就是把每台解析器所在宿主机的最小内核版本、发行版补丁日期、解析软件版本全部列出来对照各发行版的修复声明确认有没有打上对应的内核补丁。补丁固然重要但我不建议只盯补丁。真正有效的自查要回答的是“即便补丁打了网络层是否具备被探测的条件”。对着防火墙规则查一下两个方向外层到解析器主机的 UDP 探测包是否可达解析器主机回给外部的 ICMP 是否会被拦截或允许。如果 ICMP 出方向被彻底禁了攻击者的探测成本会大幅增加风险就明显下降。安全是系统工程别指望一个补丁解决所有问题。3.2 从配置里排查解析器的“随机性成色”分析完环境之后再看解析器配置。拿 BIND 举例出口端口可以选择固定端口或者随机端口options { query-source address * port 53; };这样的写法意味着所有出站查询都用固定源端口 53安全性等于裸奔即使没有 SAD DNS传统投毒也能轻松搞定。比较好的做法是让查询使用系统分配的随机高位端口同时避免使用其他服务可能占用的端口区间options { query-source address * port *; use-v4-udp-ports { 49152-65535; }; avoid-v4-udp-ports { 53; 123; }; };注意不同版本的 BIND 具体选项语法有细微差异启用前最好在测试环境跑一遍配置检查。更重要的是配置里写了一个较大的端口区间不等于实际分配均匀。我见过一个案例某企业的递归解析器配置了 49152 到 65535 的大范围但内核因为某种原因只使用其中一小段攻击者的命中率因此放大。想要确认随机化水平只能用压测工具制造大量出站查询观察源端口的分布情况。只是看一眼配置落几个参数说明不了太多。3.3 网络层基线审计观察并非打击在自查阶段有一个相对安全、但很能暴露问题的动作在解析器出口链路上做被动抓包统计 ICMP 端口不可达回包的比例。这里不是要你去复现攻击而是建立一段时间的网络行为基线。正常业务场景下解析器主机收到的 UDP 探测包数量很少ICMP 端口不可达自然也不多。如果某段时间内这类包数量突然飙升同时来源 IP 比较集中那就说明解析器已经被某种形式的端口扫描盯上了。真正的攻击者往往不会只发一个包试探他的探测行为一定会留下特征。把这些特征写进监控告警比事后排查有用得多。我自己在维护某套对外服务时曾在流量监控里看到某个时段 ICMP 不可达包数量上涨后来顺着来源 IP 查发现是一次扫描行为。虽然系统补丁齐备没有造成实际损失但也让我养成了习惯任何解析器节点都单独设置 ICMP 速率告警。这里不要忽略一个前提如果你们的上游防火墙主动丢弃 ICMP那这类监控可能什么都看不到需要在更靠近主机的位置抓包判断。4. 落地缓解方案不是只改一个参数那么简单4.1 第一梯队立刻可做的配置与策略调整先给一个结论如果你手头有一台跑在老旧内核上的递归解析器第一步是先打补丁或更换到已修复的内核版本而不是花大量时间调解析器配置。CVE-2020-25705 的核心问题在内核 ICMP 错误处理机制上解析器配置只能降低被探测的概率或影响不能根除问题。补丁级别的修复必须优先。在等待补丁窗口或者无法立即停机的场景下我建议你做以下几件立刻能生效的事在外层防火墙上限制 ICMP 出方向速率尤其限制 ICMP 端口不可达包的外发。这样做会让攻击者的探测回包变得不稳定让他无法可靠获取端口状态。给解析器单独划分网段出方向访问控制遵循最小权限原则不要让攻击者随手就能打满探测包。检查递归与权威的分离情况。如果条件允许把递归服务放在内网只对内部客户端开放权威区放在另一组服务器上。这样外部攻击者连探测包都进入不到关键解析器。这些操作都很基础但效果非常直接。SAD DNS 的探测需要大量样本才能摸清端口状态一旦回程信号被干扰或流量被限速攻击成功率会断崖式下降。4.2 中期动作把应用层随机性拉满除了系统层和网络层解析器自己的协议行为也要调整。主流解析器通常支持 0x20 编码也就是对查询域名中的大小写做随机化处理。权威服务器在响应时会原样保留查询里的大小写递归服务器把它和原始查询比对大小写不一致就拒绝。这相当于在“源端口 事务 ID”之外又多了一层随机校验。DNS Cookies 机制RFC 7873也能起到类似作用客户端在查询中夹带一个短 Cookie响应必须回带相同 Cookie否则不接受。Cookie 的随机性不是容易从 ICMP 行为里推断出来的对 SAD DNS 这一类旁路攻击有很好的缓解效果。在具体落地时解析器软件版本是否支持这些能力需要逐项确认。老版本的 BIND 或 Unbound 对 0x20 的支持情况不同dnsmasq 这类轻量工具在某些特性上可能力不从心。我的建议是别在同一个节点上贪心集成所有功能先把“1 台解析器 满足必要的扩展能力”做扎实再谈功能叠加。DNS 安全最怕的是把翻了很多层的配置堆在一台机器上最后出问题都不知道是哪个模块导致的。4.3 特殊场景DNSSEC 验证布局的取舍DNSSEC 是很多人心里的终极方案。它的思路很简单权威区对记录做数字签名递归服务器在收到响应后验证签名验证不过就丢弃。如果 DNSSEC 完整部署SAD DNS 即使猜中了源端口和事务 ID攻击者伪造的响应也无法通过验证。理论完美现实却有一点骨感DNSSEC 的“验证链”要求从根区到二级域再到具体记录层层可信任只要中间某一层不支持或配置失误整条链就会断裂。我见过不少企业把 DNSSEC 验证打开后发现业务域名解析异常最后排查出来是上游某级 DNS 不支持 DNSSEC 导致。如果你是激进派想用 DNSSEC 作为主要防线一定要先在测试环境完整跑通解析链路。更稳妥的方案是把 DNSSEC 验证当作“加分项”而不是“唯一项”与源端口随机化、0x20、DNS Cookies 配合使用。没有任何单一技术能无视条件地挡住所有投毒变种SAD DNS 之后我们应该学会的是层次化防御。4.4 一个很多人都能摘的低垂果实把递归服务藏好抛开前面所有高端技术大多数场景里最有效的方法仍然是“减少暴露面”。别把递归解析器直接绑在公网接口上更不要在监听地址里写上 0.0.0.0 然后靠防火墙碰运气。用监听配置把递归服务限制在内部网段# 示例只监听内部网段禁止公网访问 listen-on port 53 { 192.0.2.10; 10.0.1.5; }; listen-on-v6 port 53 { 2001:db8::5; }; allow-query { 10.0.0.0/8; 192.0.2.0/24; }; allow-recursion { 10.0.0.0/8; 192.0.2.0/24; };很多企业的实际状况是递归解析器并不需要向公网提供服务但因为历史原因或部署随手仍然在公网流量里裸奔。把递归能力收回到内网攻击者连探测包都送不进来很多后续问题就无从谈起。这做法听起来普通效果却被严重低估。5. 绕不开的实践细节升级、兼容与监控5.1 升级顺序先测后升别赌命打补丁这件事在 DNS 业务里尤其要谨慎。解析器是整个网络访问的中枢一旦升级出问题全站解析中断的影响范围比个别服务器宕机大得多。我踩过的坑是某回把解析器软件升级到新版本结果新版本默认开启了某种新的 UDP 行为导致部分老客户端兼容性出问题日志里大量请求超时。后来只能回滚版本再花时间逐个内核版本验证兼容性。比较稳妥的做法是搭一个影子解析器。具体来说就是准备一台配置、系统版本和线上一致的测试机只不接业务流量。把影子机的出站查询流量引到测试权威区或镜像线上日志回放观察一段时间确认 ICMP 行为、端口分布、缓存命中率都没有恶化再往生产环境推。这个验证周期至少覆盖一天尽量包含业务高峰和低谷时段。5.2 监控指标别光盯“解析成功率”日常 DNS 监控里最常被关注的是解析成功率、响应时延、缓存命中率这些指标当然重要但面对 SAD DNS 这类攻击它们的反应能力有限。攻击者探测的过程不一定导致解析失败更多是给解析器主机带来大量 UDP 包和 ICMP 回包。我建议在节点监控中增加几个专门指标单位时间内 ICMP 端口不可达包的数量、来源 IP 的聚合情况、UDP 53 端口之外的 UDP 连接数、出站临时端口分配曲线是否出现异常规律。这些指标平时数值平稳攻击发起时会出现明显抖动非常适合做告警阈值。如果只是盯着解析成功率等到攻击真的产生缓存投毒效果时可能已经波及大量用户了。我还习惯在主机侧做一个小脚本定期采集内核网络统计里的 UDP 错误计数。这个计数并不完全等同于 ICMP 不可达但它可以反映主机接收和处理 UDP 包的异常状态。攻击者探测时计数会出现明显上升把采集值接入现有监控平台基本上能做到小时级发现。5.3 频率限制与“保护性限速”的取舍缓解侧信道攻击有一个常见误区直接全量丢弃 ICMP。这么做确实切断了攻击者的回程信息但也会影响正常网络排障比如你自己想用 ping 或 traceroute 诊断链路时发现 ICMP 全没了。更合理的做法是限速而不是砍断。在网络层对 ICMP 端口不可达做速率限制保留少量流量用于排障同时对超限部分静默丢弃。这可以极大降低攻击者探测精度又不会影响常规运维工具。另一点和解析器自身相关解析器的 UDP 查询频率上限也不是越高越好。某些解析器在高并发场景下会表现出端口复用行为原本该等待释放的临时端口被立刻重新分配这种紧急分配如果实现不严谨反而让端口规律更明显。开源解析器版本更新时这类小修正常常被忽略建议升级后专门观察端口使用频率曲线确认没有任何异常波动。6. 关于 SAD DNS我的核心体会与排查清单6.1 我实际调试中感受到最大障碍是什么看了不少相关分析真正动手调试时我发现最耗费时间的不是理解攻击原理而是区分“这个 ICMP 包是主动探测造成的”还是“正常网络环境里的随机噪声”。公网上现在本身就有大量扫描流量很多主机每天都在被各种端口扫描解析器主机上的 ICMP 不可达统计里混杂着大量背景流量。我在某个测试环境里做过统计仅仅放任主机自然运行 24 小时ICMP 不可达数量就已经过千如果单纯靠数量阈值做告警一定误报频繁。必须同时看来源 IP 的集中度和时间分布扫描行为的标志是“短时间内从少量 IP 发起高频探测”而不是全网平缓增长。另一个障碍是内核参数的理解。CVE-2020-25705 的修复补丁涉及内核 ICMP 错误处理行为但不同发行版把补丁拆成了不同的小补丁有的还改了相邻模块。想确认自己系统是否彻底修复单纯看软件版本号常常不够。我建议用发行版官方提供的补丁追踪页面来核对而不是相信某个安全软件给出的“已修复”结论更不要相信自动将报错标记为无关紧要的扫描器。6.2 一张可以直接抄作业的排查清单最后整理一份我实际用过的排查清单它不依赖特定硬件或商业产品纯命令行操作也可以完成。检查项操作方式判断标准内核版本uname -a对照发行版安全公告确认是否已包含修复解析器软件版本named -v/unbound -V等确认是否包含随机化增强相关更新端口配置范围查看 named.conf 或 unbound.conf确认启用了较大范围的use-v4-udp-portsICMP 方向策略查看防火墙规则出方向不应彻底放开 ICMP 不限速出站端口分布抓包并统计源端口分布应接近随机不应出现明显分段或连续递增主机 UDP 错误计数netstat -s或ss -s基线数据和突发数据对比异常时间点要溯源这张表看起来简单每一项都要记录对应时间和版本因为 DNS 安全问题经常是多因素叠加出来的。如果我某一天接到一个“解析偶发失败”的工单第一件事就是把这张表从头拉一遍。排查结束后再根据结果补上防火墙限速或软件升级动作。对于 SAD DNS 这样的攻击最有效的应对永远不是事后的复杂操作而是平时把这些基础设施行为监控好让异常在萌芽期就暴露出来。