
SolarWinds Serv-U CVE-2026-28318: caída sin autenticación por Content-Encoding: deflate. Análisis de causa raíz (free no válido de un puntero interior -> corrupción del heap) + PoC solo de DoS. Corregido en 15.5.4 Hotfix 1.
Análisis de causa raíz + prueba de concepto de DoS. El aviso público clasifica esto como una denegación de servicio / consumo no controlado de recursos no autenticada. El análisis binario muestra que el defecto subyacente es de seguridad de memoria: una
free()inválida de un puntero interior en la ruta de decodificación HTTPdeflate, que corrompe el heap del proceso (STATUS_HEAP_CORRUPTION,0xC0000374). No es una bomba de descompresión, y la caída es independiente del tamaño descomprimido.
Afecta solo al listener HTTP/HTTPS (la ruta web/de gestión). FTP/FTPS/SFTP no están en esta ruta de código.
Cualquier solicitud HTTP que lleve Content-Encoding: deflate con un cuerpo deflate válido hace caer el
servicio. El manejador descomprime el cuerpo y luego libera el puntero al cuerpo comprimido e
intercambia el búfer descomprimido — pero ese puntero es un puntero interior dentro del búfer de
recepción HTTP (apunta al cuerpo, inmediatamente después del \r\n\r\n), no una base de asignación del heap.
Llamar a free() sobre un puntero no base y desalineado corrompe el heap y el proceso muere.
Debido a que el error está en cómo se libera el puntero del cuerpo — no en cuántos datos se producen — incluso un cuerpo comprimido de ~25 bytes (que se descomprime a unos pocos KB) lo hace caer de forma fiable. El uso de memoria no crece; no es una "bomba zip/deflate".
La rutina de decodificación deflate reside en RhinoNET.dll (la biblioteca de red de Serv-U) y se alcanza desde la
ruta de recepción HTTP (ProcessReceive) una vez que el analizador de cabeceras establece la bandera "deflate" al ver
Content-Encoding: deflate.
Simplificada, la rutina se invoca como decode(this, &bodyPtr, &bodyLen) y hace:
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
El paso 2 es el error. *bodyPtr no es un bloque asignado de forma independiente: apunta al cuerpo de la solicitud
dentro del único búfer de recepción HTTP, es decir, receive_buffer + header_length. Liberar un puntero interior
(y desalineado a 16 bytes) hace que el asignador interprete una región influenciada por el atacante como una cabecera
de chunk del heap → corrupción de metadatos del heap → 0xC0000374.
Ambas hipótesis "obvias" son incorrectas, verificado en tiempo de ejecución:
zlib1.dll!inflate estándar y respeta
avail_out en cada llamada (observado en más de 100 chunks; cero escrituras fuera de límites).prev_total + produced + 1 en cada ronda y se escribe exactamente esa cantidad — ajustado, sin desbordamiento.La corrupción es únicamente la free() inválida del paso 2.
Capturado en tiempo de ejecución (Frida) contra Serv-U 15.5.4.108 mientras se enviaba una solicitud
Content-Encoding: deflate cuyo cuerpo se descomprime a "A" * 8192:
…ce — mod 16 == 14. Las bases de asignación del heap están alineadas a 16 bytes, por lo que esto
no es una base de asignación; es un puntero interior.\r\n\r\n:
…tream\r\nContent-Length: 26\r\nConnection: close\r\n\r\n
ed c1 01 0d 00 …).ntdll.dll, excepción 0xC0000374
(STATUS_HEAP_CORRUPTION), con la llamada originándose desde la free en el manejador deflate de RhinoNET.dll.
La última operación del heap antes de la caída es exactamente esta free(bodyPtr).0xC0000374) en nuestras pruebas, y no logramos
ejecución de código. Trate esto como DoS por corrupción del heap pre-autenticación con causa raíz de seguridad
de memoria; no asuma RCE.Requiere Python 3 (solo biblioteca estándar). Apúntelo a un listener HTTP de Serv-U que esté autorizado a
probar. En Windows utiliza una llamada a Get-NetTCPConnection para leer el PID del listener y el registro de
eventos de Aplicación para confirmar la caída por corrupción del 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)
El PoC construye una solicitud mínima:
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>
Reporta PASS si el servicio se cayó (el PID del listener cambió / apareció el evento 0xC0000374) y
FAIL si sobrevivió (ya parcheado, no afectado, o el cuerpo no se procesó como deflate).
Content-Encoding entrante en el listener HTTP/HTTPS,
por ejemplo en un proxy inverso:
if ($http_content_encoding) { return 400; }
RhinoNET.dll / Serv-U.exe (base de imagen 0x180000000) para
mapear la ruta receive → header-parse → deflate-decode y localizar la free errónea.zlib
respeta avail_out, rastreando cada alloc/free/memcpy de la llamada de decodificación a disco, y un manejador
de excepciones del proceso para capturar la instrucción infractora y la pila de 0xC0000374 en el momento de la
corrupción. La liberación del puntero interior se confirmó luego volcando los bytes alrededor del puntero
liberado (cola de cabecera HTTP + cuerpo deflate crudo, desalineado a 16 bytes).Los offsets referenciados en el análisis son específicos de la compilación 15.5.4.108 y diferirán entre compilaciones.
Publicado como investigación defensiva después de la disponibilidad del parche del proveedor. El PoC es solo DoS y está destinado a pruebas autorizadas.
| Producto | SolarWinds Serv-U (FTP / MFT / File Server) |
| Vulnerable | 15.5.4 y anteriores en esa rama sin Hotfix 1 (análisis realizado en la compilación 15.5.4.108) |
| Corregido | Serv-U 15.5.4 Hotfix 1 (publicado 2026-06-04) |
| Vector | No autenticado, red (puerto HTTP/HTTPS de gestión/web) |
| Clase pública | CWE-400 Consumo no controlado de recursos · DoS · CVSS 7.5 · CISA KEV |
| Clase real | CWE-763 Liberación de puntero inválido / CWE-590 Liberación de memoria que no está en el heap → corrupción del heap |