Entrada

H2.CL request smuggling

Laboratorio de Portswigger sobre HTTP Request Smuggling

H2.CL request smuggling

Certificaciones

  • eWPT
  • eWPTXv2
  • OSWE
  • BSCP

Descripción

Este laboratorio es vulnerable a request smuggling porque el servidor front-end realiza HTTP/2 downgrading de las solicitudes HTTP/2 incluso cuando tienen una longitud ambigua

Para resolver el laboratorio, tenemos que realizar un ataque de request smuggling que provoque que el navegador de la víctima cargue y ejecute un archivo JavaScript malicioso desde el exploit server, llamando a alert(document.cookie). El usuario víctima accede a la página de inicio cada 10 segundos

Necesitamos envenenar la conexión inmediatamente antes de que el navegador de la víctima intente importar un recurso JavaScript. De lo contrario, la víctima obtendrá nuestro payload del exploit server, pero no lo ejecutará. Es posible que tengamos que repetir el ataque varias veces hasta dar con el momento adecuado


Resolución

Al acceder a la web vemos esto

Capturamos la petición con Burpsuite, la enviamos al Repeater, eliminamos las cabeceras innecesarias, pulsamos sobre Show non-printable chars y en el apartado Request atributes del Inspector cambiamos el protocolo de HTTP/2 a HTTP/1. Una vez tengamos todo esto hecho, vamos a realizar la petición, si todo funciona bien significa que la petición se puede realizar con las cabeceras que estamos usando

Lo siguiente que debemos de hacer es pulsar sobre el engranaje y descheckear la opción Update Content-Length para que no se actualice el Content-Length

Ahora vamos a cambiar el método a POST, para ello hacemos click derecho > Change request method

Ahora vamos a proceder a testear si nos encontramos ante un TE.CL o ante un CL.TE. He añadido la cabecera Transfer-Encoding con el valor chunked, esto quiere decir que vamos a enviar los datos que se proporcionan en el body en este formato. También he añadido la cabecera Content-Length porque también es necesaria

Vamos a explicar la petición. El Content-Length debe indicar un tamaño superior al del body que realmente enviamos, por eso le ponemos 6, porque es un byte mayor que el tamaño del body, el cual es 5

Si estuviéramos ante un TE.CL, el frontend procesaría el Transfer-Encoding y cortaría el body chunked después del 0\r\n\r\n (antes de la x). El backend, usando Content-Length: 6, esperaría 6 bytes pero recibiría 5 solamente, lo que provocaría un timeout

Respecto a la letra x, se pone ahí para detectar si el servidor front-end ha interpretado Transfer-Encoding y ha cortado el body antes de esa x. Si el frontend no interpreta Transfer-Encoding, la x se reenviará al backend junto con el resto del body

En este caso al enviar la petición, vemos un error. Ssegún el RFC 7230 , si la cabecera Transfer-Encoding y Content-Length están presentes, la cabecera Transfer-Encoding tiene prioridad y Content-Length se ignora https://datatracker.ietf.org/doc/html/rfc7230

Sin embargo, en este laboratorio no está pasando esto, lo que parece ser que pasa es que se están implementado directivas más restrictivas que las indicadas en el RFC 7230. He llegado a esta conclusión porque en este caso no se prioriza una de las cabeceras y se ignora la otra, si no que nos arroja un error directamente

Debido a estas medidas podemos descartar la explotación de un TE.TE, TE.CL y CL.TE

Aunque las técnicas anteriores no funcionen, todavía puede ser posible explotar un HTTP request smuggling si el servidor front-end realiza HTTP/2 downgrading de las solicitudes HTTP/2

Esta técnica es posible debido a que como HTTP/2 sigue siendo relativamente nuevo, los servidores web que lo admiten a menudo todavía tienen que comunicarse con infraestructuras back-end heredadas que solo utilizan HTTP/1. Como resultado, se ha convertido en una práctica habitual que los servidores front-end reescriban cada solicitud HTTP/2 entrante utilizando la sintaxis de HTTP/1, generando de forma efectiva su equivalente en HTTP/1. Esta solicitud downgradeada se reenvía posteriormente al servidor back-end correspondiente y cuando el servidor back-end que utiliza HTTP/1 emite una respuesta, el servidor front-end invierte este proceso para generar la respuesta HTTP/2 que devuelve al cliente

En HTTP/2 la cabecera Content-Length es opcional, es decir, si no la proporcionamos se calcula automáticamente el tamaño del body de la solicitud sin necesidad de usar la cabecera, sin embargo, durante el HTTP/2 downgrading los servidores front-end suelen añadir una cabecera Content-Length de HTTP/1, derivando su valor a partir del mecanismo integrado de longitud de HTTP/2

Para que el ataque tenga éxito necesitamos que el Content-Length que proporcionemos nosotros en la solicitud HTTP/2 llegue al servidor backend. Esto se debe a que aunque el servidor front-end utilizara la longitud implícita de HTTP/2 para determinar dónde termina la solicitud, el servidor back-end que utiliza HTTP/1 tendrá que basarse en la cabecera Content-Length derivada de la que inyectamos, lo que provocará una desincronización

Antes de seguir, vamos a capturar una solicitud por POST para verificar lo que hemos dicho anteriormente de la cabecera Content-Length cuando se usa HTTP/2. Para ello, hacemos esta búsqueda y capturamos la petición

Si desactivamos la opción Update Content-Length y bajamos el Content-Length a 11 vemos que solo se envían esos 11 bytes del body

Si quitamos la cabecera Content-Length, sigue funcionando como al inicio porque estamos usando HTTP/2. En esta solicitud podemos ver como hemos podido enviar una petición mediante HTTP/2 sin proporcionar la cabecera Content-Length. Para que esto funcione debemos de tener descheckeada la opción Update content-length

Sin embargo, si cambiamos a HTTP/1 vemos que no se envía nada

Una vez aclarado esto, vamos a empezar a testear. Lo primero que tenemos que hacer es pulsar sobre el engranaje y checkear la opción Allow HTTP/2 ALPN override para enviar solicitudes HTTP/2 incluso cuando el servidor no anuncie compatibilidad con HTTP/2 mediante ALPN. Esto nos permite comprobar si existe compatibilidad oculta con HTTP/2. Aunque en este caso no es necesario habilitar esta opción, porque ya vemos que sí que hay compatibilidad con HTTP/2, es buena práctica seguir siempre la misma metodología

Luego, en el apartado Request atributes del Inspector cambiamos el protocolo de HTTP/1 a HTTP/2

Una vez tenemos estas opciones configuradas, vamos a crear una petición para verificar si front-end realiza HTTP/2 downgrading de las solicitudes HTTP/2. Existen dos variaciones de esta técnica, H2.TE y H2.CL, en este caso vamos a probar primero con H2.CL

Para ello tenemos que construir esta solicitud

Ahora vamos a explicar la solicitud, la cabecera Content-Length: 0 es la que le dice al servidor frontend que usa HTTP/2 que la solicitud no tiene body, así que no lee nada. Una vez la solicitud llega el servidor backend, como usa HTTP/1.1 pues ocurre lo mismo, interpreta que el body está vacío siempre y cuando interprete la cabecera Content-Lenght

Vamos a proceder a enviar la petición dos veces, esto es lo que vemos después de enviar la primera solicitud

Y esto es lo que vemos después de enviar la segunda solicitud. Como vemos, se ha hecho una solicitud a un endpoint que no existe y hemos recibido un 404. Por lo tanto, podemos confirmar que estamos ante un HTTP request smuggling H2.TE

Cuando nosotros hacemos la segunda petición o cuando algún usuario accede a la webla petición que se realiza es esta

1
2
3
4
5
GET /404 HTTP/1.1\r\n
Foo: xGET / HTTP/1.1\r\n                                          ← absorbe su request line
Host: 0a440078035cb3a881b5533600370030.web-security-academy.net\r\n ← absorbe su Host
Cookie: session=abc123\r\n                                         ← absorbe sus cookies
\r\n                                                               ← cierra las cabeceras

Como vemos, al no añadir \r\n\r\n al final de nuestra petición smuggleada, la request line de la víctima se absorbe como parte del valor de la cabecera Foo y el backend usa nuestra request line (GET /404) en su lugar

Una vez sabemos la técnica a emplear, vamos a buscar en la web alguna forma de obtener un XSS. He buscado vectores clásicos de XSS pero no he encontrado nada, sin embargo, me he abierto las herramientas de desarrollador de Chrome y he visto que se cargan dos archivo JS

He pulsado Ctrl + Shift + R en la web para borrar la caché y que se hicieran todas las peticiones nuevamente. Una vez hecho esto, he ido al Logger de Burpsuite para ver esas peticiones y me he dado cuenta que se hacen 3 peticiones cada cierto tiempo

Esta es la solicitud de la vamos a intentar aprovecharnos

He ido borrando partes del path hasta que he visto que me ha devuelto un redirect. La forma en la que lo he ido haciendo es borrando primero los valores proporcionados a los parámetros de consulta, luego hacía una petición, después borraba los parámetros de consulta y hacía otra petición, posteriormente borraba la / y hacía otra petición y lo mismo con la extensión del archivo

Esto por si solo no tienen ningún impacto, sin embargo, si conseguimos manipular la cabecera Host para que apunte a nuestro Exploit server podríamos hacer que la web cargue un archivo JavaScript malicioso

Antes de nada, debemos de preparar la ruta donde vamos a alojar nuestro archivo para que coincida con la ruta a la que se hace el redirect

No sirve de nada hacer la petición así porque es como si estuviéramos haciendo la petición directamente a nuestro Exploit server

Para poder explotar esto, debemos de hacerlo mediante un ataque de HTTP request smuggling. Si funciona, nos debería de hacer un redirect a nuestro Exploit server. Enviamos la petición 2 veces y esto es lo que obtenemos como segunda respuesta

Como vemos, se hace el redirect pero se no se está haciendo a nuestro Host. Esto ocurre porque cuando se produce la absorción que hemos mencionado antes, la cabecera Foo: x se encarga de invalidar la request line pero la cabecera Host está debajo de la request line y esa no la podemos invalidar

Podría darse el caso de que funcione pero eso depende del comportamiento del servidor backend al recibir dos cabeceras iguales. Por ejemplo, esta tabla muestra lo que ocurre cuando en una petición hay dos cabeceras duplicadas dependiendo de la tecnología que se esté usando

Servidor / FrameworkComportamiento con duplicados
NginxSe queda con la última
ApacheSe queda con la primera
IISLas concatena con coma
Node.js (http)Las concatena con coma
Flask/PythonSe queda con la última
DjangoLas concatena con coma
TomcatVaría según la cabecera
HAProxy (proxy)Reenvía ambas al backend
VarnishReenvía ambas

La alternativa a esto es hacer que la petición smuggleada sea interpretada como una petición completa. Para ello vamos a usar la misma técnica que hemos usado anteriormente en los laboratorios donde explotamos un HTTP request smuggling TE.CL

Esta técnica consiste en inflar el Content-Length, indicando un tamaño superior al del body que realmente enviamos en la peticion smuggleadaComo el body ocupa 9 bytes, utilizamos un Content-Length de 10. Esto hace que el back-end no dé por finalizada la petición tras leer esos 9 bytes, sino que espere un byte adicional, el cual pertenecerá a la siguiente petición HTTP. Este comportamiento es el que permite que la siguiente petición quede parcialmente absorbida por la petición smuggleada y se produzca la desincronización

Como vemos, ahora el redirect nos lo hace a nuestro Exploit server. Lo que hemos hecho es utilizar HTTP request smuggling para convertir un on-site redirect en un open redirect

Si pulsamos en Follow redirect vemos el payload que tenemos alojado en nuestro servidor

Para comprobar que aparece el alert() vamos a tener que repetir varias veces el ataque. La primera petición la hacemos desde Burpsuite

Y la segunda desde el navegador. Tenemos que repetir este proceso hasta que nos salga un alert() en el navegador

Necesitamos hacer el proceso varias veces porque tenemos que envenenar la conexión inmediatamente antes de que el navegador de la víctima intente importar el recurso JavaScript. De lo contrario, la víctima obtendrá nuestro payload del exploit server, pero no lo ejecutará

Esta entrada está licenciada bajo CC BY 4.0 por el autor.