根因分析 + DoS 概念验证。 公开公告将此漏洞归类为未认证的拒绝服务 / 不受控制的资源消耗。二进制分析表明,根本缺陷是内存安全问题:在 HTTP
deflate解码路径中对内部指针执行了无效的free(),这会破坏进程堆(STATUS_HEAP_CORRUPTION、0xC0000374)。这不是解压炸弹,且崩溃与解压后的大小无关。
| 产品 | SolarWinds Serv-U(FTP / MFT / 文件服务器) |
| 受影响版本 | 该分支中未安装 Hotfix 1 的 15.5.4 及更早版本(分析基于构建版本 15.5.4.108) |
| 已修复版本 | Serv-U 15.5.4 Hotfix 1(发布于 2026-06-04) |
| 攻击向量 | 未认证、网络(HTTP/HTTPS 管理/Web 端口) |
| 公开分类 | CWE-400 不受控制的资源消耗 · DoS · CVSS 7.5 · CISA KEV |
| 实际分类 | CWE-763 释放无效指针 / CWE-590 释放非堆内存 → 堆损坏 |
仅影响 HTTP/HTTPS 监听器(Web/管理路径)。FTP/FTPS/SFTP 不在此代码路径上。
任何携带 Content-Encoding: deflate 且具有有效 deflate 主体的 HTTP 请求都会使服务崩溃。处理程序解压主体,然后释放指向压缩主体的指针并换入解压后的缓冲区——但该指针是 HTTP 接收缓冲区内的内部指针(它指向主体,紧跟在 \r\n\r\n 之后),并非堆分配基址。对非基址、未对齐的指针调用 free() 会破坏堆,导致进程崩溃。
因为缺陷出在主体指针的释放方式——而非产出了多少数据——即使约 25 字节的压缩主体(解压后仅几 KB)也能可靠地使其崩溃。内存使用量不会增长;这不是"zip/deflate 炸弹"。
deflate 解码例程位于 RhinoNET.dll(Serv-U 的网络库)中,一旦头解析器在检测到 Content-Encoding: deflate 时设置"deflate"标志,便会从 HTTP 接收路径(ProcessReceive)进入该例程。
简化而言,该例程以 decode(this, &bodyPtr, &bodyLen) 形式被调用,其操作如下:
1. inflate bodyPtr[0..bodyLen] -> grows an accumulator buffer `acc` (stock zlib, bounded, correct)
2. free(*bodyPtr) <-- *bodyPtr is an INTERIOR pointer into the receive buffer
3. *bodyPtr = acc // replace compressed body with decompressed buffer
4. *bodyLen = total
第 2 步就是缺陷所在。*bodyPtr 不是独立分配的块:它指向请求主体,该主体位于单个 HTTP 接收缓冲区内部,即 receive_buffer + header_length。释放内部(且 16 字节未对齐)指针会使分配器将受攻击者影响的区域解析为堆块头 → 堆元数据损坏 → 0xC0000374。
两种"显而易见"的假设都是错误的,这一点已在运行时得到验证:
zlib1.dll!inflate,每次调用都遵守 avail_out(在 100 多个块上观察到;零越界写入)。prev_total + produced + 1 分配,并恰好写入这么多——紧凑,无溢出。损坏完全源于第 2 步中的无效 free()。
以下证据是在运行时(Frida)针对 Serv-U 15.5.4.108 捕获的,当时发送了一个 Content-Encoding: deflate 请求,其主体解压后为 "A" * 8192:
…ce — mod 16 == 14。 堆分配基址按 16 字节对齐,因此这不是分配基址;它是一个内部指针。\r\n\r\n 结尾:
…tream\r\nContent-Length: 26\r\nConnection: close\r\n\r\n
ed c1 01 0d 00 …)。ntdll.dll,异常 0xC0000374(STATUS_HEAP_CORRUPTION),调用源自 RhinoNET.dll deflate 处理例程中的 free。崩溃前的最后一次堆操作正是这次 free(bodyPtr)。0xC0000374),并且我们未能实现代码执行。 请将其视为具有内存安全根本原因的预认证堆损坏 DoS;不要假设存在 RCE。需要 Python 3(仅标准库)。将其指向您获授权测试的 Serv-U HTTP 监听器。在 Windows 上,它通过一次 Get-NetTCPConnection 调用来读取监听器 PID,并检查应用程序事件日志以确认堆损坏崩溃。
python poc_verify.py # default 127.0.0.1:80, tiny packet, 1 shot
python poc_verify.py --host <ip> --port 80
python poc_verify.py --big # body decompresses to 8192 bytes
python poc_verify.py --shots 3
python poc_verify.py --no-events # skip event-log check (no privileges needed)
PoC 会构建一个最小请求:
POST / HTTP/1.1
Host: <target>
Content-Encoding: deflate
Content-Type: application/octet-stream
Content-Length: <n>
Connection: close
<raw-deflate body — even a few dozen bytes is enough>
如果服务崩溃(监听器 PID 发生变化 / 出现 0xC0000374 事件),它将报告 PASS;如果服务存活(已修补、不受影响,或主体未按 deflate 处理),则报告 FAIL。
Content-Encoding,例如在反向代理上:
if ($http_content_encoding) { return 400; }
RhinoNET.dll / Serv-U.exe(映像基址 0x180000000)进行参数化 PE 反汇编,以梳理接收 → 头解析 → deflate 解码路径,并定位错误的 free。zlib 遵守 avail_out,将解码调用中的每次 alloc/free/memcpy 追踪到磁盘,并使用进程异常处理器在损坏发生的时刻捕获故障指令及 0xC0000374 栈。随后,通过转储被释放指针周围的字节(HTTP 头部尾部 + 原始 deflate 主体,16 字节未对齐),确认了内部指针释放。分析中引用的偏移量特定于构建版本 15.5.4.108,不同构建版本会有所不同。
在厂商补丁可用后作为防御性研究发布。PoC 仅限 DoS,仅供授权测试使用。