HTTP/2 Downgrading
Cuando un reverse proxy sirve contenido al usuario final con HTTP/2 (conexión frontend) pero hace peticiones a servidores backend que usan HTTP/1.1 (conexión backend), podemos hablar de HTTP/2 downgrading. Este tipo de implementación sigue siendo común hoy en día, haciendo posible reintroducir el HTTP Request Smuggling en el contexto de HTTP/2, pero sólo allí donde HTTP downgrades ocurren.

En lugar de lidiar directamente con HTTP/2, mandamos peticiones HTTP/2 al frontend para que se genere la correspondiente petición HTTP/1 que es mandada al backend, por lo que se crea una desincronización.
Idealmente, el proxy debería convertir de manera segura una única petición HTTP/2 en otra única petición HTTP/1 equivalente. Esto es verdadero sólo en la teoría a veces. Cada implementación de proxy puede gestionar la conversión de forma diferente, haciendo introducir una petición maliciosa HTTP/1.1 en el backend posible.
El Comportamiento Esperado
Antes de adentrarnos en la request smuggling, entendamos cómo una petición sería traducida de HTTP/2 a HTTP/1.1. Toma la siguiente petición POST como ejemplo:

El proceso es simple. Los headers y el body de la petición HTTP/2 se pasan directamente a la petición HTTP/1.1. Fíjate que la petición HTTP/2 incluye un header content-length. Recuerda que HTTP/2 no lo usa para nada pero HTTP/1.1 lo requiere. Es por esto que cualquier navegador moderno incluirá este header para lidiar premeditadamente con los downgrades de HTTP. En el caso de los proxies que usaremos, el header Host es añadido después de los otros headers basado en el contenido del pseudo-header :authority.
H2.CL
Como ya hemos mencionado, el header Content-Length no tiene significado alguno para HTTP/2 debido a que la longitud del body de la petición está especificado ambiguamente. Pero nada nos impide añadir un header Content-Length a una petición HTTP/2. Si ocurre un downgrade HTTP, el proxy pasará el content-length añadido de HTTP/2 a HTTP/1.1, permitiendo la desincronización. Para entender esto mejor, considera lo que pasaría con la siguiente petición HTTP/2:

El proxy recibe la petición HTTP/2 de la conexión frontend. Al traducir la petición a HTTP/1.1, simplemente pasa el contenido del header Content-Length a la conexión backend. Cuando el servidor backend lee la petición, reconoce el Content-Length inyectado como válido. Como el Content-Length en nuestro ejemplo es 0, el backend piensa que es una petición sin body. Lo que sea que venga después de los headers (la petición HTTP/2 original), será tratado como el principio de una nueva petición. Como la palabra HELLO no es una petición HTTP/1.1 completa, el backend esperará hasta que más información llegue para completarla.
La conexión backend ahora está desicronizada. Si otro usuario manda una petición, será concatenada al HELLO que se ha quedado colgando en la conexión backend. Si, por ejemplo, otro usuario hace una petición justo después, esto es lo que pasaría:

Fíjate cómo la petición actual se mezcla con el valor colgado. Esto altera efectivamente la petición del usuario víctima, que puede ser abusado por el atacante de varias maneras.
H2.TE
También podemos añadir un header Transfer-Encoding: chunked a la petición de frontend HTTP/2 y el proxy puede que lo pase al backend HTTP/1.1 sin modificarlo. Si el servidor backend prioriza este header para determinar el tamaño del body de la petición, podemos desincronizar la conexión backend de nuevo. Aquí está cómo se vería la petición HTTP/2:

El efecto es el mismo en el caso H2.CL. La primera petición es ahora una petición chunked. El primer chunk es de tamaño 0, por lo que el backend piensa que ha terminado. El resto del body de la petición HTTP/2 es tratado como una petición nueva, contaminando la siguiente petición.
Inyección CRLF
CRLF es la notación corta para nueva linea. CR significa Carriage Return, equivalente al carácter con código ASCII 0xD, también representado como \r. LF significa Line Feed, el carácter con código ASCII 0xA también representado como \n. CRLF es la secuencia de ambos caracteres \r\n, que es usado en HTTP/1.1 como un delimitador entre headers, y también separa los headers del body (usando un \r\n doble).
Como los paquetes de HTTP/2 manejan información binaria, insertar cualquier carácter en un campo de la petición es imposible. Esto supone un problema al traducir las peticiones a HTTP/1.1, ya que algunos caracteres como \r\n representan delimitadores entre headers. Si podemos inyectar CRLF en un header HTTP/2, puede ser traducido en el proxy directamente a HTTP/1.1, lo que será interpretado como separador de heder, permitiéndonos contrabandear peticiones.
Para entender esto, miraremos a qué podría pasar si mandamos la siguiente petición HTTP/2:

La petición HTTP/1.1 resultante ahora tiene un header adicional. Fíjate que no estamos limitados a inyectar headers, sino que podemos también contrabandear peticiones completas de esta manera.
CRLF injection no está restringida a headers HTTP/2 sólo. Cualquier lugar donde mandes un \r\n que potencialmente acabe en una petición HTTP/1.1 podría potencialmente obtener los mismos resultados. Fíjate que cada proxy intentará sanitizar las peticiones de manera diferente, por lo que los métodos pueden cambiar dependiendo de tu objetivo.
Ejemplo Práctico
En este ejemplo explotaremos una vulnerabilidad H2.CL en una versión antigua del proxy Varnish. En este laboratorio, el proxy usa un sólo backend para manejar las peticiones entrantes de todos los usuarios para que podamos usar la vulnerabilidad H2.CL para interferir con las peticiones de otros usuarios.
La aplicación simula una red social extremadamente simple. En este caso, puedes ver tus propios posts (uno sólo) y darle like o dislike. Usaremos la vulnerabilidad H2.CL para forzar a otros usuarios a darle like a nuestro post (el lab simula una víctima).
Primero analizaremos cómo funciona la aplicación. Con una simple inspección, podemos encontrar dos cosas importantes:
- La aplicación guarda la cookie
sessiden el navegador con tu nombre de usuario asignado para trackear tu identidad. - Para darle like a una publicación, una petición GET es mandada a
/post/like/<post_id>, dondepost_ides el id de la publicación que será likeada. Podemos adivinar con seguridad que la aplicación identificará qué usuario quiere darle like con la cookiesessid.
Para forzar que otros usuarios den like a nuestro post, podemos mandar la siguiente petición POST:

Fíjate que estamos usando el método POST para la petición HTTP/2, porque queremos mandar un body. Como hemos configurado content-length: 0, el backend pensará que la petición POST no tiene body y lo que sea que venga después será interpretado como una petición separada.
Estamos contrabandeando una petición GET incompleta a /post/like/12315198742342. Esta petición corresponde a darle like a nuestro post. Como la petición está incompleta, el backend esperará a que llegue más información para completarla. Si otro usuario manda una petición al servidor justo después (a cualquier URL), su petición será mezclada con la nuestra. Como resultado, el servidor recibirá lo siguiente:

Fíjate en cómo la petición de nuestra víctima se convierte en una petición para darle like a nuestro post. La URL original pedida por la víctima se ignora ya que se convierte en el header X: que inyectamos en la petición contrabandeada. Como resultado, el servidor backend procesará un like a nuestro post pero con la cookie de la víctima.
Para que funcione en Burp Suite, debemos capturar una petición HTTP/2 hacia el sitio y usar el Repeater para modificarla hasta que se vea así:

Asegúrate de que no dejas ninguna nueva línea despues de X: f. Si existieran dichos espacios, la linea de la petición de la siguiente petición no se concatenará correctamente.
Asegúrate de comprobar en la esquina superior derecha que la petición repetida es en efecto una petición HTTP/2. Como nuestro ataque requiere configurar el header Content-Length: 0, también necesitaremos desmarcar el campo "Update Content-Length" en el Repeater. Si no, el Repeater recalculará la longitud correcta de la petición.

Una vez mandado el payload espera un poco para que la víctima mande una petición. Puede que necesites intentar el ataque un par de veces para coger la petición a tiempo. Asegúrate de no mandar más peticiones durante los 30 segundos posteriores a contaminar al conexión del backend.
