Entrada

HTTP request smuggling, obfuscating the TE header

Laboratorio de Portswigger sobre HTTP Request Smuggling

HTTP request smuggling, obfuscating the TE header

Certificaciones

  • eWPT
  • eWPTXv2
  • OSWE
  • BSCP

Descripción

Este laboratorio tiene un servidor front-end y un servidor back-end y los dos servidores manejan las cabeceras de las peticiones de forma diferenteEl servidor front-end rechaza solicitudes que no utilicen el método GET o POST

Para resolver el laboratorio, debemos enviar una solicitud smuggleada al servidor back-end, de forma que la siguiente solicitud procesada por el servidor back-end parezca que utiliza el método GPOST

Aunque el laboratorio admite HTTP/2, la solución prevista requiere técnicas que solo son posibles en HTTP/1Es posible cambiar manualmente de protocolo en el Repeater desde la sección Request attributes del Inspector


Guía de HTTP request smuggling

Antes de completar este laboratorio es recomendable leerse esta guía de HTTP request smuggling https://justice-reaper.github.io/posts/HTTP-Request-Smuggling-Guide/

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 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 que no hay ningún error. Por lo tanto podemos descartar que se trate de un TE.CL

Puede que estemos ante un CL.TE, así que vamos a detectarlo con esta petición

Si estamos ante un CL.TE, el frontend enviará esos 6 primeros bytes al backend y al no indicar donde finaliza el body chunked con el 0 pues el backend se quedará a la espera de ese 0 y por lo tanto, provocará un timeout. Con el 6 en el Content-Length le indicamos al frontend que mande solo esos 6 primeros bytes al backendEn principio valdría cualquier valor que nos permita enviar al backend un body chunked malformado, los de portswigger recomiendan 4 por ejemplo

Respecto a la x, pues es igual que en el caso anterior, la usamos para detectar si el front-end ha interpretado el Transfer-Encoding y ha cortado el body antes de la x

Al enviar la petición pasa lo que hemos mencionado anteriormente y el servidor backend nos devuelve un error. No podemos confirmar que sea un CL.TE porque no sabemos si el error proviene del servidor frontend o del servidor backend

Si enviamos una petición bien formada, no da ningún error. Esto significa que el servidor backend o el servidor frontend o ambos soportan transfer encoding

Para aclarar las dudas, vamos a ejecutar un HTTP request smuggling CL.TE y TE.CL completo. Primero vamos a probar con un CL.TE

Vamos a explicar los valores que se usan en la peticiónEl Content-Length es 49 porque el body de la petición ocupa 49 bytes

Luego, el body chunked ocupa 14 bytes pero como hay que ponerlo en hexadecimal ponemos la letra eLa e indica lo que ocupa el body chunked y el 0 especifica donde termina el body chunked

Respecto a la cabecera Foo: x, la petición smuggleada no termina con \r\n\r\n intencionalmente y esto hace que cuando la víctima envíe su petición, el backend la interprete como continuación de la petición smuggleadaLa cabecera Foo: x absorbe la request line de la petición de la víctima y la cabecera Host proveniente de la petición de la víctima completa la petición smuggleada, haciendo que sea válida en HTTP/1.1Dependiendo del laboratorio, puede que el backend requiera cabeceras específicas adicionales para considerar la petición válida

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 hemos explicado esto, debemos enviar la petición 2 veces, la primera vez obtenemos una respuesta normal y la segunda vez también

Ahora vamos a realizar un HTTP request smuggling TE.CL

Vamos a explicar la peticiónEl Content-Length debe indicar un tamaño superior al del body que realmente enviamosComo el body ocupa 10 bytes, utilizamos un Content-Length de 11. Esto hace que el back-end no dé por finalizada la petición tras leer esos 10 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

Luego, el valor 9e es 158 en hexadecimal e indica el tamaño del chunk que va a recibir el frontend y el 0 indica la que ahí es donde termina el body chunked

Y por último, el Content-Length es 4 porque es el número de bytes que ocupa la primera línea del body chunkedEsto se hace para que el backend lea solo hasta ahí

Una vez hemos explicado esto, debemos enviar la petición 2 veces, la primera vez obtenemos una respuesta normal y la segunda vez también

Como antes hemos visto que bien el servidor backend o el frontend soportan Transfer-Encoding y que ahora no obtenemos ningún error al realizar los ataques completos, puede ser que estemos ante un TE.TE. Si esto último es cierto, como ambos servidores interpretan la cabecera pues no puede haber desincronización y por lo tanto, no podemos confirmar la vulnerabilidad de forma clara

La forma de resolver esto es inducir a uno de los dos servidores a que no la procese ofuscando la cabecera de alguna forma. Existen prácticamente infinitas formas de ofuscar la cabecera Transfer-Encoding. Por ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Transfer-Encoding: xchunked

Transfer-Encoding : chunked

Transfer-Encoding: chunked
Transfer-Encoding: x

Transfer-Encoding:[tab]chunked

[space]Transfer-Encoding: chunked

X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked

El orden de las cabeceras puede variar, es decir, tenemos que probar a poner la cabecera ofuscada primero y luego la cabecera normal y viceversaEste comportamiento depende del la tecnología que se use, por lo tanto, es necesario probar todos los payloads mencionados anteriormente si no sabemos que se está usando. 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

Si intentamos provocar un timeout y verificar si es un CL.TE, seguimos obteniendo la misma respuesta de antes

Sin embargo, si intentamos provocar un timeout y verificar si es un TE.CL, obtenemos un timeout

Para confirmar esto, vamos a ejecutar un HTTP request smuggling TE.CL completo. Al realizar la primera petición vemos que todo se ve normal

Y al hacer la segunda petición vemos que ha funcionado el ataque. Esto significa que hemos conseguido ofuscar la cabecera correctamente y crear una discrepanciaLa discrepancia la hemos provocado nosotros al ofuscar una cabecera Transfer-Encoding, esto hace que bien el servidor backend o el servidor frontend use Content-Length en vez de Transfer-Encoding

Lo que pasaba antes es que tanto el servidor frontend como el backend interpretaban la cabecera Transfer-Encoding y según el RFC 7230 , si ambas cabeceras están presentes, la cabecera Transfer-Encoding tiene prioridad y Content-Length se ignora https://datatracker.ietf.org/doc/html/rfc7230

Una vez hemos hecho esto, podemos afirmar que estamos ante un HTTP request smuggling TE.CL. Una vez ya confirmada la vulnerabilidad, vamos a resolver el laboratorioPara ello, debemos de hacer una petición utilizando el método GPOST. Así que necesitamos ajustar el valor del Content-Length nuevamente

Una vez hayamos hecho esto, tenemos que enviar dos peticionesCuando enviemos la primera veremos que todo se ha ejecutado correctamente

Al efectuar la segunda petición, veremos estoLo cual significa que hemos completado el laboratorio correctamente, ya que hemos hecho una petición utilizando el método GPOST

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