针对6个dnsmasq CVE(CVE-2026-2291, 4890, 4891, 4892, 4893, 5172)的自动化漏洞验证工具
自动化黑盒工具,用于验证 6 个 dnsmasq 漏洞(2026 年 5 月)。向在线 DUT 发送攻击数据包并报告 PASS/FAIL —— 无需访问源代码。
# Set DUT DNS to your laptop's WAN IP via GUI first, then:
sudo python3 dnsmasq_cve_verify.py --laptop <YOUR_WAN_IP> --dut <DUT_LAN_IP> --dut-pass <SSH_PASS>
# Example:
sudo python3 dnsmasq_cve_verify.py --laptop 10.0.0.211 --dut 192.168.1.1 --dut-pass '12345Asdf@'
| CVE | CVSS | 类型 | 攻击向量 | 受影响功能 |
|---|---|---|---|---|
| CVE-2026-2291 | 9.2 | 堆缓冲区溢出 | 远程 | extract_name() — 始终启用 |
| CVE-2026-5172 | 7.5 | 越界读取 / 崩溃 | 远程 | extract_addresses() — 始终启用 |
| CVE-2026-4890 | 7.5 | 无限循环 DoS | 远程 | NSEC 位图解析(--dnssec) |
| CVE-2026-4891 | 5.3 | 堆越界读取 | 远程 | RRSIG 验证(--dnssec) |
| CVE-2026-4892 | 8.4 | 堆溢出 → root | 本地/相邻 | DHCPv6 CLID(--dhcp-script + DHCPv6) |
| CVE-2026-4893 | 5.3 | 验证绕过 | 远程 | ECS 源检查(--add-subnet) |
根本原因: union bigname 声明了 char name[MAXDNAME],但转义字符可将名称扩展为 2*MAXDNAME+1 字节,导致堆溢出。
测试方法: 发送包含高位字符(0x80+)域名的 DNS 查询,这些字符会在内部被转义为 \DDD(每个输入字节 4 字节)。如果 dnsmasq 崩溃或停止响应,则存在漏洞。
修复后行为: 优雅地拒绝超长名称(FORMERR/REFUSED)或使用增大的缓冲区。
根本原因: 伪造的 rdlen 字段使 extract_name() 将指针推进到记录末尾之后。剩余字节下溢产生一个巨大值 → 大规模越界读取 → 崩溃。
测试方法: 发送包含 CNAME 记录的 DNS 响应,其中 rdlen 小于实际编码名称的长度。如果 dnsmasq 崩溃,则存在漏洞。
修复后行为: 验证 extract_name() 后指针保持在声明的 rdlen 边界内。
根本原因: NSEC 类型位图解析按 p[1] 推进,而不是 p[1]+2(缺少窗口头大小)。当 bitmap_length=0 时,指针永不推进 → 无限循环。
测试方法: 发送精心构造的 NSEC 记录,window=0, bitmap_length=0。如果 dnsmasq 停止响应所有查询(挂起而非崩溃),则存在漏洞。可在 RRSIG 验证之前被利用。
修复后行为: 按 p[1]+2 推进并跳过零长度位图。
根本原因: RRSIG 中的 rdlen 未根据最小大小(18 + 签名者名称)进行验证。计算出的签名长度下溢为负 → 被视为巨大值 → 越界读取。
测试方法: 发送 rdlen=10(远低于最小 31+ 字节)的 RRSIG 记录。崩溃 = 存在漏洞。
修复后行为: 在计算签名长度之前验证 rdlen >= fixed_fields + signer_name_length。
根本原因: DHCPv6 CLID(最长 65535 字节)通过 sprintf("%.2x") 十六进制编码到 daemon->packet(5131 字节)。3000 字节的 CLID → 6000 字节的十六进制字符串 → 溢出。辅助进程以 root 身份运行。
测试方法: 发送包含 3000 字节 Client Identifier 的 DHCPv6 SOLICIT。需要 IPv6 相邻和配置了 --dhcp-script。辅助进程崩溃 = 存在漏洞。
修复后行为: 在十六进制编码前截断或验证 CLID 长度。
注意: 某些构建使用 -DNO_DHCP6 编译,不受此 CVE 影响。
根本原因: process_reply() 将 OPT 记录长度(约 23 字节)而不是完整数据包长度传递给 check_source()。所有边界检查均失败 → 该函数始终返回 1(有效)。
测试方法: 发送包含伪造源前缀的 EDNS Client Subnet 选项的 DNS 查询。如果 dnsmasq 未经验证就回显 ECS,则存在漏洞。
修复后行为: 将完整数据包长度传递给 check_source(),从而根据 RFC 7871 第 9.2 节进行正确的边界检查。
升级到 dnsmasq 2.92rel2(推荐)
dnsmasq_cve_verify.py)主要 QA 工具。运行在测试笔记本上,向 DUT 发送攻击数据包,并针对每个 CVE 报告明确的 PASS/FAIL。除用于状态检查的只读 SSH 访问外,无需对 DUT 进行任何修改。
┌─────────────────────────────────────────────────────────────────────┐
│ Testing Laptop │
│ │
│ LAN interface WAN interface │
│ <LAPTOP_LAN_IP> <LAPTOP_WAN_IP> │
│ │ │ │
│ │ ┌────┴──────────────┐ │
│ │ │ Malicious DNS │ │
│ │ │ Server (port 53) │ │
│ │ └────┬──────────────┘ │
│ │ │ │
└────────┼───────────────────────────────┼────────────────────────────┘
│ LAN subnet │ WAN subnet
│ │
┌────────┼───────────────────────────────┼────────────────────────────┐
│ │ │ │
│ LAN: <DUT_LAN_IP> WAN: <DUT_WAN_IP> │
│ (LAN gateway) (WAN uplink) │
│ │
│ DUT (Linksys Router) │
│ dnsmasq (any version < 2.92rel2) │
│ │
│ resolv-file=/etc/resolv.conf │
│ → nameserver <LAPTOP_WAN_IP> ← set via GUI, forwards to us │
│ │
└─────────────────────────────────────────────────────────────────────┘
Data flow:
1. Tool sends DNS query to DUT LAN IP (port 53)
2. DUT's dnsmasq can't resolve locally → forwards upstream to LAPTOP_WAN_IP
3. Our malicious server on WAN interface replies with exploit payload
4. DUT's dnsmasq processes the malicious response → crash/hang/survive
5. Tool checks DUT state via SSH (read-only)
示例设置(你的 IP 可能不同):
| Role | IP (example) |
|---|---|
| 笔记本 LAN | 192.168.1.254 |
| 笔记本 WAN | 10.0.0.211 |
| DUT LAN | 192.168.1.1 |
| DUT WAN | 10.0.0.214 |
关键要求:笔记本 WAN IP 与 DUT WAN IP 必须在同一子网,这样 DUT 才能将笔记本作为上游 DNS 服务器访问。
┌──────────┐ ┌───────────┐ ┌──────────────────┐ ┌──────────┐
│ SETUP │ ──► │ TRIGGER │ ──► │ STATE INSPECT │ ──► │ VERDICT │
│ │ │ │ │ │ │ │
│ Start │ │ Send DNS │ │ SSH to DUT: │ │ PASS: │
│ malicious│ │ query to │ │ - pidof dnsmasq │ │ survived │
│ DNS srv │ │ DUT→DUT │ │ - PID changed? │ │ │
│ on WAN │ │ forwards │ │ - dmesg crash? │ │ FAIL: │
│ interface│ │ to us→we │ │ - /var/log/msg │ │ crashed/ │
│ (10.0.0. │ │ reply w/ │ │ │ │ hung │
│ 211:53) │ │ exploit │ │ Liveness query │ │ │
│ │ │ payload │ │ (version.bind) │ │ │
└──────────┘ └───────────┘ └──────────────────┘ └──────────┘
NOTE: Tool does NOT modify DUT settings. User must set DNS to 10.0.0.211 via GUI.
paramiko 的 Python 3.6+(pip install paramiko)使用两根网线将测试笔记本连接到 DUT:
| 笔记本端口 | 连接至 | 用途 |
|---|---|---|
| LAN 端口 | DUT LAN 端口 | SSH 访问 + 向 DUT 发送 DNS 查询 |
| WAN 端口 | DUT WAN 子网(例如上游交换机/调制解调器端口) | 充当上游 DNS 服务器 |
连接后,记下笔记本的 IP:
# Find your IPs
ip addr show | grep "inet "
# Example output:
# inet 192.168.1.254/24 ... ← this is your LAN IP
# inet 10.0.0.211/24 ... ← this is your WAN IP (use this for --laptop)
http://192.168.1.1 或 http://myrouter.local10.0.0.211)cd /path/to/dnsmasq-cve-2026/
# Run all 6 CVE tests:
sudo python3 dnsmasq_cve_verify.py --laptop <YOUR_WAN_IP> --dut <DUT_LAN_IP> --dut-pass <SSH_PASSWORD>
# Example:
sudo python3 dnsmasq_cve_verify.py --laptop 10.0.0.211 --dut 192.168.1.1 --dut-pass '12345Asdf@'
该工具将:
| 问题 | 解决方法 |
|---|
所有已测试的 Linksys 路由器在其生产构建配置中,对这 6 个 CVE 均不具备实际可利用性。危险功能(DNSSEC、通过 dnsmasq 的 DHCPv6)要么未编译,要么未配置。仍然建议打补丁作为纵深防御。
修复前(dnsmasq 2.78,无 DNSSEC 构建):
修复前(dnsmasq 2.90,启用 DNSSEC):
修复后(dnsmasq 2.92rel2 或已应用向后移植补丁): 所有 6 个 CVE → PASS
paramiko 的 Python 3.6+(pip install paramiko)test_dnsmasq_cve_remote.py)仅进行轻量级版本检查 — 查询 version.bind 以确定 dnsmasq 版本是否低于修复版本。无需 SSH、无需设置、无需利用载荷。
python3 test_dnsmasq_cve_remote.py 192.168.1.1
test_dnsmasq_cve_on_device.sh)通过 SSH/串口直接在 DUT 上运行。检查二进制版本和编译选项。
scp test_dnsmasq_cve_on_device.sh [email protected]:/tmp/
ssh [email protected] "sh /tmp/test_dnsmasq_cve_on_device.sh"
malicious_dns_server.py)用于手动测试的独立利用服务器。运行它,将 DUT 的上游 DNS 指向它,然后触发对 crash-5172.evil.test、crash-2291.evil.test 等的查询。
sudo python3 malicious_dns_server.py --port 53
# Then on DUT: configure upstream → this host
# Then trigger: dig @192.168.1.1 crash-5172.evil.test
应用补丁并刷入新固件后,重新运行:
sudo python3 dnsmasq_cve_verify.py --laptop <YOUR_WAN_IP> --dut <DUT_LAN_IP> --dut-pass <SSH_PASS>
# Expected: all 6 PASS
| "DUT 未将查询转发给我们" | 确认第 2 步已正确完成。检查笔记本 WAN IP 与 GUI 中输入的一致。 |
| "无法连接 DUT" | 验证 SSH 凭据。手动尝试:ssh [email protected]。 |
| "无法绑定端口 53" | 使用 sudo 运行。或使用 --dns-port 5353(需要手动配置 DUT)。 |
| "版本:未知" | DUT 可能未将 dnsmasq 放在标准路径中。工具仍能正确测试。 |
| CVE | 结果 | 原因 |
|---|
| CVE-2026-2291 | PASS | DNSSEC 未编译 |
| CVE-2026-4890 | PASS | DNSSEC 未编译 |
| CVE-2026-4891 | PASS | DNSSEC 未编译 |
| CVE-2026-4892 | PASS | dnsmasq 未提供 DHCPv6 服务(使用了单独的 DHCPv6 服务器) |
| CVE-2026-4893 | PASS | 仅逻辑缺陷 — 无崩溃 |
| CVE-2026-5172 | PASS | 经受住了利用(2.78 中不存在易受攻击的代码路径) |
| CVE | 结果 | 原因 |
|---|
| CVE-2026-2291 | PASS | DNSSEC 未编译 |
| CVE-2026-4890 | PASS | DNSSEC 未编译 |
| CVE-2026-4891 | PASS | DNSSEC 未编译 |
| CVE-2026-4892 | PASS | DHCPv6 未编译 |
| CVE-2026-4893 | PASS | 仅逻辑缺陷 — 无崩溃 |
| CVE-2026-5172 | PASS | 经受住了各种利用变体 |
| CVE | 结果 | 原因 |
|---|
| CVE-2026-2291 | PASS | DNSSEC 未编译 — 不可利用 |
| CVE-2026-4890 | PASS | DNSSEC 未编译 — 不可利用 |
| CVE-2026-4891 | PASS | DNSSEC 未编译 — 不可利用 |
| CVE-2026-4892 | PASS/FAIL | DHCPv6 已编译且 dhcp-script 已启用 |
| CVE-2026-4893 | PASS | 仅逻辑缺陷 — 无崩溃(仅基于版本) |
| CVE-2026-5172 | PASS | 2.78 中不存在 blockdata_expand 路径 |
| CVE | 结果 | 原因 |
|---|
| CVE-2026-2291 | FAIL | 通过转义名称导致的堆溢出 |
| CVE-2026-4890 | FAIL | 无限循环(挂起) |
| CVE-2026-4891 | FAIL | RRSIG 越界读取崩溃 |
| CVE-2026-4892 | PASS/FAIL | 取决于 DHCPv6 + 脚本配置 |
| CVE-2026-4893 | PASS | 仅逻辑缺陷 — 无崩溃 |
| CVE-2026-5172 | FAIL | 通过伪造 rdlen 导致越界读取 |
| 标志 | 默认值 | 说明 |
|---|
--laptop | (必填) | 笔记本的 WAN IP(在此处绑定恶意 DNS 服务器) |
--dut | 192.168.1.1 | DUT 的 LAN IP(SSH 和 DNS 查询发送到此地址) |
--dut-user | root | DUT SSH 用户名 |
--dut-pass | (提示输入) | DUT SSH 密码 |
--dns-port | 53 | 恶意 DNS 服务器使用的端口 |
--cve | 全部 6 个 | 要测试的特定 CVE(可重复) |
| 工具 | Python | Root | SSH | 网络 |
|---|
dnsmasq_cve_verify.py | 3.6+ paramiko | 是(端口 53) | 是(只读) | 与 DUT 之间的 LAN + WAN |
test_dnsmasq_cve_remote.py | 3.6+ 标准库 | 否 | 否 | 到 DUT 的 UDP 53 |
test_dnsmasq_cve_on_device.sh | 不适用(shell) | 否 | 在 DUT 上运行 | 不适用 |
malicious_dns_server.py | 3.6+ 标准库 | 是(端口 53) | 否 | DUT 转发给我们 |