
SolarWinds Serv-U CVE-2026-28318: unauthenticated Content-Encoding: deflate crash. Root-cause analysis (invalid free of an interior pointer -> heap corruption) + DoS-only PoC. Fixed in 15.5.4 Hotfix 1.
Root-cause analysis + DoS proof-of-concept. The public advisory classifies this as an unauthenticated denial-of-service / uncontrolled resource consumption. Binary analysis shows the underlying defect is memory-safety: an invalid
free()of an interior pointer in the HTTPdeflatedecode path, which corrupts the process heap (STATUS_HEAP_CORRUPTION,0xC0000374). It is not a decompression bomb, and the crash is independent of the decompressed size.
Affects only the HTTP/HTTPS listener (the web/management path). FTP/FTPS/SFTP are not on this code path.
Any HTTP request carrying Content-Encoding: deflate with a valid deflate body crashes the
service. The handler decompresses the body, then frees the pointer to the compressed body and
swaps in the decompressed buffer — but that pointer is an interior pointer into the HTTP receive
buffer (it points at the body, immediately after the \r\n\r\n), not a heap allocation base.
Calling free() on a non-base, unaligned pointer corrupts the heap and the process dies.
Because the bug is in how the body pointer is freed — not in how much data is produced — even a ~25-byte compressed body (that decompresses to a few KB) reliably crashes it. Memory usage does not grow; it is not a "zip/deflate bomb."
The deflate-decode routine lives in RhinoNET.dll (Serv-U's network library) and is reached from the
HTTP receive path (ProcessReceive) once the header parser sets the "deflate" flag on seeing
Content-Encoding: deflate.
Simplified, the routine is called as decode(this, &bodyPtr, &bodyLen) and does:
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
Step 2 is the bug. *bodyPtr is not an independently-allocated block: it points to the request body
inside the single HTTP receive buffer, i.e. receive_buffer + header_length. Freeing an interior
(and 16-byte-misaligned) pointer makes the allocator parse an attacker-influenced region as a heap
chunk header → heap metadata corruption → 0xC0000374.
Both of the "obvious" hypotheses are wrong, verified at runtime:
zlib1.dll!inflate and honors
avail_out on every call (observed across 100+ chunks; zero out-of-bounds writes).prev_total + produced + 1 each round and written exactly that much — tight, no overflow.The corruption is solely the invalid free() in step 2.
Captured at runtime (Frida) against Serv-U 15.5.4.108 while sending a Content-Encoding: deflate
request whose body decompresses to "A" * 8192:
…ce — mod 16 == 14. Heap allocation bases are 16-byte aligned, so this
is not an allocation base; it is an interior pointer.\r\n\r\n:
…tream\r\nContent-Length: 26\r\nConnection: close\r\n\r\n
ed c1 01 0d 00 …).ntdll.dll, exception 0xC0000374
(STATUS_HEAP_CORRUPTION), with the call originating from the free in the RhinoNET.dll deflate
handler. The last heap operation before the crash is exactly this free(bodyPtr).0xC0000374) in our testing, and we did not
achieve code execution. Treat this as pre-auth heap-corruption DoS with a memory-safety root
cause; do not assume RCE.Requires Python 3 (standard library only). Point it at a Serv-U HTTP listener you are authorized to
test. On Windows it uses one Get-NetTCPConnection call to read the listener PID and the Application
event log to confirm the heap-corruption crash.
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)
The PoC builds a minimal request:
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>
It reports PASS if the service crashed (listener PID changed / 0xC0000374 event appeared) and
FAIL if it survived (already patched, not affected, or the body was not processed as deflate).
Content-Encoding on the HTTP/HTTPS
listener, e.g. on a reverse proxy:
if ($http_content_encoding) { return 400; }
RhinoNET.dll / Serv-U.exe (image base 0x180000000) to
map the receive → header-parse → deflate-decode path and locate the erroneous free.zlib
honors avail_out, tracing every alloc/free/memcpy of the decode call to disk, and a process
exception handler to capture the faulting instruction and the 0xC0000374 stack at the moment of
corruption. The interior-pointer free was then confirmed by dumping the bytes around the freed
pointer (HTTP header tail + raw-deflate body, 16-byte-misaligned).Offsets referenced in analysis are specific to build 15.5.4.108 and will differ across builds.
Published as defensive research after vendor patch availability. PoC is DoS-only and intended for authorized testing.
| Product | SolarWinds Serv-U (FTP / MFT / File Server) |
| Vulnerable | 15.5.4 and earlier in that branch without Hotfix 1 (analysis done on build 15.5.4.108) |
| Fixed | Serv-U 15.5.4 Hotfix 1 (released 2026-06-04) |
| Vector | Unauthenticated, network (HTTP/HTTPS management/web port) |
| Public class | CWE-400 Uncontrolled Resource Consumption · DoS · CVSS 7.5 · CISA KEV |
| Actual class | CWE-763 Release of Invalid Pointer / CWE-590 Free of Memory not on the Heap → heap corruption |