原文标题: Stack Overflows, Heap Overflows, and Existential Dread (SonicWall SMA100 CVE-2025-40596, CVE-2025-40597 and CVE-2025-40598)
原文链接: Stack Overflows, Heap Overflows, and Existential Dread (SonicWall SMA100 CVE-2025-40596, CVE-2025-40597 and CVE-2025-40598)
2025年,我们坚信行业内存在一项不言而喻的默契:每台网络设备都必须包含至少一个可轻易规避的HTTP头解析漏洞——最好是在认证前阶段。若涉及sscanf函数,则可额外加分。
若果真如此,那真是太棒了!SonicWall的SMA100系列已自豪地完成了配额——甚至可能符合额外奖励的条件。
我们最初的探索之旅始于分析 SonicWall N-day 漏洞(SonicBoom, From Stolen Tokens to Remote Shells - SonicWall SMA (CVE-2023-44221, CVE-2024-38475)),当时它正受到我们友好的 APT 团队的密切关注。然而,在漏洞百出的攻击头和反向代理的迷雾中,我们偶然发现了一些漏洞,它们仿佛是 C 语言编程早期的遗留物。
虽然我们理解(并认同)这些漏洞最终难以利用——甚至在某些情况下目前无法利用——但它们的存在本身,坦率地说,就令人失望。由畸形HTTP头部触发的认证前堆栈和堆溢出本不应再发生。然而……我们却遭遇了。
来吧,与我们一同感叹。我们将带您回顾我们是如何发现这些漏洞的,发现了什么,以及为什么网络安全工作让人感到如此有保障。
在这篇博客中,我们将详细介绍在SonicWall SMA100系列中发现的三个漏洞——所有漏洞均在固件版本10.2.1.15上得到确认:
CVE-2025-40596 - 基于栈的缓冲区溢出(认证前)
CVE-2025-40597 - 基于堆的缓冲区溢出(认证前)
CVE-2025-40598 - 反射型跨站脚本攻击(认证前,需用户交互)
SonicWall 的安全公告可在此处查看: Security Advisory。
让我们深入探讨。
在审查负责处理 SonicWall SSLVPN 传入 HTTP 请求的 /usr/src/EasyAccess/bin/httpd 二进制文件时,我们发现了一个简单的基于堆栈的缓冲区溢出漏洞。值得注意的是,IDA无法反编译该漏洞函数。
httpd 二进制文件包含一系列条件检查,用于将传入请求映射到特定功能。如果请求 URI 以 /__api__/ 开头,则触发以下逻辑。
首先,二进制文件对传入 URI 与 /__api__/ 字符串进行 strncasecmp 比较:

如果请求的 URI 的前 9 个字节以 /__api__/ 开头,执行流程将跳转到 loc_444F0 块。让我们来仔细看看这个块。
loc_444F0 块的逻辑很简单——它调用著名的 sscanf 函数来解析用户输入。具体来说,它将用户提供的 URI 的一部分复制到存储在 rcx 寄存器中的内存地址中。该地址本身来自于 [rsp+898h+var_878] 的栈变量。

位于 [rsp+898h+var_878] 的堆栈变量大小为 0x800 字节,并在函数执行过程中被清零。
该函数的意图似乎很明确——它旨在解析以下格式的传入 URI:
sscanf 调用首先提取 2 个字节到 v1 中,然后将最后一个斜杠后面的所有字符复制到 0x800 字节的栈缓冲区中——当然,没有进行任何边界检查。
...因为还能出什么问题呢?
重现此问题非常简单——只需使用以下一行代码即可触发:
如果远程连接断开,这强烈表明目标系统存在漏洞。然而,如果你发送一个较小的数据包——例如大约2000字节——服务器会返回400 Bad Request错误。
例如:
以下是在 gdb 中崩溃的具体表现:

由于SonicWall的/usr/src/EasyAccess/bin/httpd二进制文件启用了栈保护功能,使得此次溢出攻击的危害程度仅为中等。该溢出位置紧邻stack canary和返回地址,中间没有其他可被利用的变量。
然而,这仍然是一个发生在 2025 年 SSL-VPN 中认证前路径上的栈溢出漏洞。
一个已经令人沮丧,两个?唉……
在继续审查httpd二进制文件时,我们发现了一个名为mod_httprp.so的共享对象,该对象似乎处理了设备HTTP解析的大部分功能。其名称中的httprp可能代表“HTTP反向代理”——该库负责检查传入的HTTP请求,以实现各种SonicWall特定功能,包括内容过滤、远程桌面访问等。
该漏洞源于mod_httprp.so在解析Host:header时触发的越界堆写入操作。
这个漏洞很特别——不仅因为它涉及sprintf函数,还因为有人特意使用了“安全”版本,却依然突破了所有防护措施。
为了简单起见,我们将其分解开来——让我们来探究其根本原因。
首先,通过简单的 calloc(0x80) 调用分配一个堆块,返回一个已清零的内存区域。该块的指针存储在 v21 中,随后被传递给 __sprintf_chk 调用。
现在,你可能会想——__sprintf_chk 不是应该是个“安全”版本吗?这怎么可能存在漏洞?
确实会。但前提是你使用方式错误。
虽然__sprintf_chk函数期望接收一个带边界检查的size参数,但SonicWall开发者却选择了传递-1(即0xFFFFFFFFFFFFFFFF)。
对于在家中尝试的读者——这不是进行边界检查的正确方式。
SSL-VPN开发者们,你们离成功如此之近,却又如此之远……
这意味着该函数将继续从提供的Host:header的字节中复制到0x80大小的堆块中……直到遇到空字节。
让我们看看 v21 在复制前的内容:它指向我们刚刚通过 calloc 分配的 0x80 字节大小的堆块——正如预期的那样,完全是空的。紧接着它的是什么?额外的堆分配内存。
而这正是事情变得有趣的地方。
复制完成后,我们发现相邻堆块的元数据已被我们的控制数据覆盖。
这……不太理想。
重现此问题同样简单——只需执行以下一行命令即可触发:
关于利用此漏洞实现完全的认证前远程代码执行(RCE)——由于目标Web服务器上存在大量后台任务和脚本持续与之交互,我们在堆内存元数据操作过程中无法可靠地与服务器竞争。
更进一步的是,在审查可用CGI端点时,我们意外发现了一个经典的反射型XSS漏洞。
触发该漏洞极为简单——只需访问radiusChallengeLogin端点,并通过state参数注入XSS有效载荷。该值会直接反射到响应中,且未经过任何过滤。
值得注意的是,尽管SMA100 SSLVPN提供了WAF功能,但该功能在管理接口上似乎完全被禁用——这意味着即使是如下所示的基本有效载荷也足以触发该漏洞:

结果?就像 2005 年(或 2025 年的 Fortinet)发出的老式alert(1),它想要找回自己的漏洞。
一段令人怀念的往事——也是一个提醒,有些事情永远不会改变。
https://x.x.x.x/__api__/v1/login
https://x.x.x.x/__api__/v1/login
import requests; requests.get("1dcK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5i4K6u0W2P5q4)9J5k6i4S2Q4x3X3g2^5i4K6u0r3i4K6g2X3i4K6g2X3j5i4m8A6i4K6g2X3i4K6g2X3i4K6u0r3N6U0q4Q4x3V1j5`."+'A'*3000,verify=False)
import requests; requests.get("352K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5i4K6u0W2P5q4)9J5k6i4S2Q4x3X3g2^5i4K6u0r3i4K6g2X3i4K6g2X3j5i4m8A6i4K6g2X3i4K6g2X3i4K6u0r3N6U0q4Q4x3V1j5`."+'A'*3000,verify=False)
import requests; requests.get("14eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5i4K6u0W2P5q4)9J5k6i4S2Q4x3X3g2^5i4K6u0r3i4K6g2X3i4K6g2X3j5i4m8A6i4K6g2X3i4K6g2X3i4K6u0r3N6U0q4Q4x3V1j5`." + 'A'*2000, verify=False)
import requests; requests.get("6f0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5i4K6u0W2P5q4)9J5k6i4S2Q4x3X3g2^5i4K6u0r3i4K6g2X3i4K6g2X3j5i4m8A6i4K6g2X3i4K6g2X3i4K6u0r3N6U0q4Q4x3V1j5`." + 'A'*2000, verify=False)
v21 = calloc(0x80, 1);
.text:0000000000031DDE lea rax, asc_43502+1 ; "/"
.text:0000000000031DE5 lea r8, aHttps+1 ; "https://"
.text:0000000000031DEC lea rcx, aShostSSSSS+9 ; "%s%s%s%s"
.text:0000000000031DF3 mov r9, r15
.text:0000000000031DF6 mov esi, 1
.text:0000000000031DFB mov [rsp+1220h+var_1218], rax
.text:0000000000031E00 mov rdx, [rbp+var_1108]
.text:0000000000031E07 mov rdi, r12
.text:0000000000031E0A xor eax, eax
.text:0000000000031E0C mov [rsp+1220h+var_1220], rdx
.text:0000000000031E10 mov rdx, 0FFFFFFFFFFFFFFFFh
.text:0000000000031E17 call ___sprintf_chk
v21 = calloc(0x80, 1);
.text:0000000000031DDE lea rax, asc_43502+1 ; "/"
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!