
SolarWinds Serv-U CVE-2026-28318: crash senza autenticazione tramite Content-Encoding: deflate. Analisi della causa principale (free non valido di un puntatore interno -> corruzione dell'heap) + PoC solo DoS. Corretto nella 15.5.4 Hotfix 1.
Analisi della causa principale + proof-of-concept DoS. L'avviso pubblico classifica la vulnerabilità come un denial-of-service / consumo di risorse non controllato non autenticato. L'analisi binaria mostra che il difetto sottostante è memory-safety: un
free()non valido di un puntatore interno nel percorso di decodificadeflateHTTP, che corrompe l'heap del processo (STATUS_HEAP_CORRUPTION,0xC0000374). Non è una bomba di decompressione, e il crash è indipendente dalla dimensione decompressa.
Interessa solo il listener HTTP/HTTPS (il percorso web/gestione). FTP/FTPS/SFTP non sono su questo percorso di codice.
Qualsiasi richiesta HTTP con Content-Encoding: deflate e corpo deflate valido manda in crash il
servizio. L'handler decomprime il corpo, poi libera il puntatore al corpo compresso e
sostituisce con il buffer decompresso — ma quel puntatore è un **puntatore interno al buffer di ricezione HTTP
** (punta al corpo, subito dopo \r\n\r\n), non una base di allocazione dell'heap.
Chiamare free() su un puntatore non di base e non allineato corrompe l'heap e il processo muore.
Poiché il bug sta in come viene liberato il puntatore al corpo — non in quanti dati vengono prodotti — anche un corpo compresso di ~25 byte (che si decompone a pochi KB) lo manda in crash in modo affidabile. L'uso della memoria non cresce; non è una "bomba zip/deflate".
La routine di decodifica deflate si trova in RhinoNET.dll (la libreria di rete di Serv-U) e viene raggiunta dal
percorso di ricezione HTTP (ProcessReceive) quando il parser delle intestazioni imposta il flag "deflate" alla vista di
Content-Encoding: deflate`.
Semplificando, la routine è chiamata come decode(this, &bodyPtr, &bodyLen) e fa:
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
Il passaggio 2 è il bug. *bodyPtr non è un blocco allocato indipendentemente: punta al corpo della richiesta
all'interno dell'unico buffer di ricezione HTTP, cioè receive_buffer + header_length. Liberare
un puntatore interno (e non allineato a 16 byte) fa sì che l'allocatore interpreti una regione influenzata dall'attaccante come un
header di chunk dell'heap → corruzione dei metadati dell'heap → 0xC0000374.
Entrambe le ipotesi "ovvie" sono sbagliate, come verificato a runtime:
zlib1.dll!inflate e rispetta
avail_out a ogni chiamata (osservato su oltre 100 chunk; zero scritture fuori dai limiti).prev_total + produced + 1 a ogni ciclo e scritto esattamente di quella quantità — preciso, nessun overflow.La corruzione è dovuta esclusivamente alla free() non valida nel passaggio 2.
Acquisito a runtime (Frida) su Serv-U 15.5.4.108 mentre si invia una richiesta Content-Encoding: deflate
il cui corpo si decompone in "A" * 8192:
…ce — mod 16 == 14. Le basi di allocazione dell'heap sono allineate a 16 byte, quindi questo
non è una base di allocazione; è un puntatore interno.\r\n\r\n:
…tream\r\nContent-Length: 26\r\nConnection: close\r\n\r\ned c1 01 0d 00 …).ntdll.dll, eccezione 0xC0000374
(STATUS_HEAP_CORRUPTION), con la chiamata che origina dalla free nella routine deflate di RhinoNET.dll
handler. L'ultima operazione sull'heap prima del crash è esattamente questa free(bodyPtr).0xC0000374) nei nostri test, e non abbiamo
ottenuto esecuzione di codice. Tratta questo come DoS pre-autenticazione con corruzione dell'heap con una causa principale di memory-safety;
non presupporre RCE.Richiede Python 3 (solo libreria standard). Puntalo a un listener HTTP Serv-U che sei autorizzato a
testare. Su Windows usa una chiamata Get-NetTCPConnection per leggere il PID del listener e il
log eventi Applicazione per confermare il crash per corruzione dell'heap.
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)
Il PoC costruisce una richiesta minimale:
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>
Riporta PASS se il servizio è andato in crash (PID del listener cambiato / evento 0xC0000374 apparso) e
FAIL se è sopravvissuto (già corretto, non interessato, o il corpo non è stato elaborato come deflate).
Content-Encoding in ingresso sul listener HTTP/HTTPS
listener, ad esempio su un reverse proxy:
if ($http_content_encoding) { return 400; }
RhinoNET.dll / Serv-U.exe (base immagine 0x180000000) per
mappare il percorso receive → header-parse → deflate-decode e individuare la free erronea.zlib
rispetta avail_out, tracciando ogni alloc/free/memcpy della chiamata di decodifica su disco, e un gestore di eccezioni di processo
per catturare l'istruzione faulting e lo stack 0xC0000374 al momento della
corruzione. La free del puntatore interno è stata poi confermata scaricando i byte attorno al puntatore liberato
(coda intestazione HTTP + corpo deflate grezzo, non allineato a 16 byte).Gli offset citati nell'analisi sono specifici della build 15.5.4.108 e differiranno tra le build.
Pubblicato come ricerca difensiva dopo la disponibilità della patch del vendor. Il PoC è solo DoS ed è pensato per test autorizzati.
| Prodotto | SolarWinds Serv-U (FTP / MFT / File Server) |
| Vulnerabile | 15.5.4 e precedenti in quel ramo senza Hotfix 1 (analisi eseguita sulla build 15.5.4.108) |
| Corretto | Serv-U 15.5.4 Hotfix 1 (rilasciato il 2026-06-04) |
| Vettore | Non autenticato, di rete (porta di gestione/web HTTP/HTTPS) |
| Classificazione pubblica | CWE-400 Uncontrolled Resource Consumption · DoS · CVSS 7.5 · CISA KEV |
| Classe effettiva | CWE-763 Release of Invalid Pointer / CWE-590 Free of Memory not on the Heap → corruzione dell'heap |