
Detecta y explota vulnerabilidades de contrabando de solicitudes HTTP mediante la conversión de HTTP/2 a HTTP/1.1, utilizando técnicas automatizadas de contrabando de cabeceras para identificar discrepancias en el análisis del backend.
Esta herramienta ayuda a detectar y explotar el contrabando de solicitudes HTTP en aquellos casos en que se puede lograr mediante la conversión HTTP/2 -> HTTP/1.1 por parte del servidor frontend.
El esquema es el siguiente:
El atacante busca encontrar una solicitud tal que sea vista como dos solicitudes separadas por el servidor backend.
Si la conexión HTTP/1.1 frontend<->backend utiliza keep-alive, el frontend podría enviar solicitudes de otros usuarios en la misma conexión. Si somos capaces de "envenenar" la conexión con una solicitud parcial que llega después de una legítima, podemos recuperar la solicitud de otro usuario.
Otros escenarios posibles incluyen la omisión de la protección del servidor frontend y sus reescrituras, el envenenamiento de caché o el engaño de caché.
Para obtener más información sobre el contrabando de solicitudes HTTP, consulte la Academia de Seguridad Web de Portswigger.
En HTTP/2, todos los nombres y valores de las cabeceras HTTP son binarios. Eso significa que técnicamente pueden contener espacios adicionales o incluso saltos de línea.
RFC7540#10.3 establece que las implementaciones que traducen solicitudes HTTP/2 a HTTP/1 deben tener en cuenta las limitaciones en el conjunto de caracteres que surgen de dicha conversión; la mayoría de las implementaciones efectivamente los rechazan. A pesar de esto, esperamos encontrar algunas que permitan dichas cabeceras. Estas corromperán la solicitud HTTP/1.1 convertida hacia el backend.
Otro punto es que algunas correcciones recientes relacionadas con el contrabando de solicitudes HTTP podrían implementarse solo para los analizadores HTTP/1.1.
En general, esperamos que existan implementaciones de HTTP/2 que no estén muy al tanto de las investigaciones recientes sobre contrabando de solicitudes HTTP en HTTP/1.1 y no incluyan las mitigaciones correspondientes.
¡Sorprendentemente, sí!
Encontré una posibilidad de introducir de contrabando una cabecera con un carácter de espacio a través de Cloudflare, abriendo así la puerta al contrabando entre Cloudflare y el cliente (en caso de que el software de un cliente de Cloudflare acepte y recorte nombres de cabeceras). Aquí está la publicación del blog.
También hay otro informe de recompensa por errores que aún no es público. Este hace uso del hecho de que el software personalizado no filtra los saltos de línea en las cabeceras HTTP/2, y el contrabando ocurre al 100% (puedo ver las solicitudes de otros usuarios).
Sin embargo, entiendo que este tipo de vulnerabilidades deben ser frustrantemente raras: a diferencia de HTTP/1.1, no hay tantas implementaciones de HTTP/2, y la mayoría están diseñadas teniendo en cuenta la seguridad, por lo que rechazan cabeceras sospechosas o inválidas.
La herramienta tiene un subcomando que intenta detectar automáticamente si un objetivo es vulnerable al ataque de contrabando de solicitudes HTTP. El algoritmo detrás de esta funcionalidad se describe en esta sección.
Para realizar un ataque de contrabando de solicitudes HTTP, primero necesitamos "introducir de contrabando" una única cabecera (ya sea Content-Length o Transfer-Encoding). Esto significa que necesitamos enviar una cabecera que a) controle dónde termina el cuerpo de la solicitud y b) no sea procesada por el frontend pero sí por el backend.
Esto generalmente se logra modificando una cabecera de alguna manera: agregando espacios o tabulaciones al final de su nombre, reemplazando el valor por uno semi-equivalente, etc.
La idea básica del algoritmo de detección de vulnerabilidades es detectar si el servidor realmente procesa una cabecera de contrabando como si fuera Content-Length o Transfer-Encoding. Hacemos esto enviando múltiples solicitudes: algunas con valores válidos y otras con valores inválidos para la cabecera. Luego intentamos detectar si hay alguna forma de distinguir las respuestas de estos dos grupos.
Por eso la salida de la herramienta no contiene las palabras "vulnerable/invulnerable": solo indica si puede distinguir las respuestas que llegaron de las solicitudes de estos dos grupos.
La herramienta considera que dos conjuntos de respuestas HTTP son distinguibles si se cumple al menos una de las siguientes dos condiciones:
Los tiempos de espera se tratan como un valor de código de estado único, no igual a ningún otro; por lo tanto, la herramienta reemplaza el esquema clásico de "detección por temporización".
Consideremos un ejemplo. Supongamos que estamos tratando de introducir de contrabando la cabecera transfer-encoding reemplazando el guion por un guion bajo.
Si resulta que el servidor responde con el estado 400 cada vez que enviamos transfer_encoding:zalupa y se cuelga cuando es transfer_encoding:chunked, podemos decir que probablemente el servidor procesa la cabecera como el valor para transfer encoding. Teóricamente, podría ser el servidor frontend o el backend.
El primer caso no es interesante ya que podríamos enviar la versión no contrabandeada de la cabecera de todos modos, y el segundo caso es lo que estamos buscando. Como todo ocurre a través de HTTP/2, el primer caso se puede evitar la mayoría de las veces: el servidor HTTP/2 determina el final del cuerpo de la solicitud de otra manera que no está relacionada con las cabeceras de la solicitud y nunca espera que el cuerpo esté en el formato fragmentado HTTP/1.1 (aquel con longitudes hexadecimales de fragmento).
Las variaciones concretas de las técnicas de detección son:
Enviamos una versión contrabandeada de Transfer-Encoding: chunked (p. ej., transfer_encoding:chunked) y diferentes cuerpos: válido es 0\r\n\r\n e inválido es 999\r\n.
En caso de que las respuestas sean diferentes, podemos estar seguros de que el servidor backend recibe y procesa la cabecera contrabandeada. No hay razón para que el frontend haga esto: HTTP/2 no utiliza el formato fragmentado que enviamos, por lo que sería inválido.
Esperamos que el backend se cuelgue (es decir, que la solicitud llegue a tiempo de espera) para las solicitudes inválidas, ya que espera que lleguen más datos.
Esta es la variante de detección más fiable: si el servidor se cuelga al leer el cuerpo, algo probablemente ha ido mal, ya que no hay uso para la codificación de transferencia HTTP/1.1 en una solicitud HTTP/2.
Enviamos una versión contrabandeada de Transfer-Encoding y nuevamente diferentes cuerpos: 0\r\n\r\n como cuerpo válido y X\r\n\r\n como inválido.
El caso es el mismo que el anterior, pero en lugar de leer el cuerpo, esperamos que el backend al menos lo valide.
Enviamos una versión contrabandeada de la cabecera Content-Length con valores 1 y -1.
Al enviar múltiples pares de solicitudes válidas/inválidas, podemos reducir la posibilidad de un falso positivo aleatorio. Por otro lado, podemos detenernos temprano si vemos que no hay forma de separar las respuestas de las solicitudes "válidas" de las respuestas de las solicitudes "inválidas".
La herramienta intenta emplear múltiples técnicas de contrabando modificando una cabecera de varias maneras. Ninguna de ellas es nueva y a la vez no obvia.
Para introducir una cabecera de contrabando, agregamos un espacio al final de la misma. Es el método más común y clásico. Esperamos que la cabecera no sea procesada por el frontend, pero se envíe tal cual al backend, que elimina el espacio.
La herramienta prueba una variedad de caracteres como espacios: incluye , \t, \v, \x00 y los Unicode.
Para introducir una cabecera de contrabando, reemplazamos el guion (-) por un guion bajo (_). Si el backend está inspirado en CGI de alguna manera, podría convertir cabeceras como Header-Name al formato HEADER_NAME; por lo tanto, el guion se convertirá en guion bajo de todos modos. Al determinar cómo analizar el cuerpo, dicho backend supuestamente solicita el valor de CONTENT_LENGTH / TRANSFER_ENCODING de su diccionario de cabeceras, y estará allí.
Este es específico de HTTP/2. Como HTTP/2 es un protocolo binario, podemos intentar enviar saltos de línea en el nombre o valor de la cabecera. El estándar lo prohíbe, pero esperamos encontrar una implementación que aún lo acepte.
Durante la conversión HTTP/2 -> HTTP/1.1, la cabecera se divide en dos cabeceras diferentes, lo que significa que la solicitud se verá diferente para el backend.
Para introducir una cabecera de contrabando, colocamos su nombre y valor después de un salto de línea: una cabecera con el nombre "Transfer-Encoding" y el valor "chunked" se convierte en una con el nombre "fake" y el valor "fake\r\ntransfer-encoding: chunked".
Supongamos que el backend utiliza algún lenguaje de alto nivel y no realiza suficiente validación de cabeceras. En ese caso, podría convertir los nombres a mayúsculas antes de hacer cualquier otra cosa y hacerlo utilizando funciones conscientes de Unicode. Afortunadamente, TRANSFER-ENCODING contiene la letra S, que es ſ (\u017f) en mayúscula.
De manera similar, podemos buscar un backend que convierta el valor de Transfer-Encoding a minúsculas: enviamos chunKed en lugar de chunked con \u212a en lugar de K.
Por supuesto, se requiere que el frontend pase nombres/valores de cabeceras UTF-8 al backend.
Para instalar la herramienta, ejecute go install github.com/neex/http2smugl@latest.
La herramienta contiene dos subcomandos: request y detect. El primero es solo para construir solicitudes HTTP/2: la mayoría de las herramientas cliente no aceptan cabeceras inválidas, por lo que es útil tener una que envíe la entrada del usuario al servidor tal cual.
El otro es detect. Intenta varias técnicas de contrabando de solicitudes HTTP para detectar si el objetivo es vulnerable. El algoritmo de detección es complicado; agradecería que lo leyera y me enviara sus comentarios. Se describe a continuación en la sección correspondiente.
http2smugl requestUtilice este subcomando para enviar una solicitud http2 (probablemente un poco malformada). El primer parámetro es la URL, y los demás son simplemente cabeceras en el formato nombre:valor (nótese que no hay espacio después de los dos puntos). Se admite el escape con barra invertida: puede usar códigos de escape \r, \n y \xXX. Por ejemplo, para enviar un nombre de cabecera que contenga dos puntos, use \x3a (p. ej., nombre\x3acon\x3ados:puntos:valor).
http2smugl detectEste subcomando intenta detectar el contrabando de solicitudes HTTP utilizando varias técnicas. Para usarlo, simplemente ejecute http2smugl detect [URL HTTPS].
El comando solo mostrará algo si puede detectar que el servidor analiza una cabecera contrabandeada. Para entender qué significa esto, lea la sección correspondiente.
Se ha implementado un soporte experimental para HTTP/3 (quic). Sin embargo, no sugiero usarlo, ya que no encontré ningún error relacionado con HTTP/3.
Para usar HTTP/3 con el subcomando request, proporcione el protocolo https+h3:// en la URL en lugar de solo https. Lo mismo se admite en el comando detect.
También hay una bandera --try-http3 para el subcomando request, que cambia el comportamiento en caso de que el protocolo no esté especificado en la URL (solo el nombre de host). Si la bandera está presente, el comando probará el protocolo https+h3 para dichas entradas en la línea de comandos o en el archivo de objetivos (además de HTTP/2). Por ejemplo, http2smugl detect --try-http3 www.example.com probará tanto HTTP/3 como HTTP/2, pero http2smugl detect --try-http3 https://www.example.com/ solo probará HTTP/2.
En esta sección, describo algunos casos en los que la herramienta dice que las respuestas son "distinguibles", pero no podría existir ninguna vulnerabilidad.
El Elastic Load Balancer de Amazon implementa múltiples mitigaciones contra el contrabando de solicitudes HTTP. Aunque no todas rechazan la solicitud por defecto, ELB no reutilizará una conexión después de enviar una solicitud con cabeceras "sospechosas". Por lo tanto, un ataque real no es posible.
Para detectar si está tratando con ELB, puede usar la cabecera de respuesta Server. Si está filtrada, puede enviar una solicitud con la cabecera content__length (nótese el doble guion bajo) y valor -1: si es 400, probablemente sea ELB u otro WAF (ver más abajo).
Apache Traffic Server se utiliza principalmente en Yahoo. Procesa HTTP/2 de una manera inusual: lo convierte a HTTP/1.1 en memoria y luego vuelve a analizar la solicitud resultante. Por lo tanto, aunque técnicamente se podría "contrabandear" una cabecera, no hay forma de que resulte en una vulnerabilidad: podría enviar los mismos bytes a una conexión HTTP/1.1.
La forma más fácil de detectar que está tratando con ATS (además de la cabecera Server) es enviando una solicitud TRACE. Si hay una cabecera Max-Forwards: 0 presente en la solicitud, ATS devolverá una respuesta a las solicitudes TRACE por defecto sin reenviarlas al backend.
Parece que Microsoft IIS admite la decodificación de la codificación fragmentada dentro de los cuerpos HTTP/2. Ese es un comportamiento extraño; sin embargo, es inocuo desde el punto de vista de la seguridad.
Para detectar si está tratando con IIS, puede enviar una solicitud con una cabecera "transfer-encoding:chunked" y un cuerpo fragmentado incorrecto. Si ve Microsoft-HTTPAPI o algo similar en la cabecera Server, eso es.
Los WAF intentan detectar cabeceras sospechosas; ese es su trabajo. A veces esto da como resultado que la herramienta diga "distinguible": el WAF podría bloquear algo como content_length:-1 y permitir content_length:1 solo porque sus filtros llegan a estas decisiones.
Si ve un WAF en la cabecera Server, probablemente sea un falso positivo.
Si tiene alguna idea sobre este tema, póngase en contacto conmigo a través de @emil_lerner en Twitter o @neexemil en Telegram, o publique un problema aquí en GitHub.
Ambos valores son inválidos desde el punto de vista del frontend: hay otro mecanismo para determinar la longitud del cuerpo en HTTP/2, y en ambos casos no se envía ningún cuerpo de solicitud. Si las respuestas son diferentes, suponemos que es el servidor backend quien analizó las cabeceras.
Este método es el menos fiable: el frontend podría emitir diferentes errores cuando Content-Length tiene un valor inválido y cuando no coincide con la longitud real.