HAProxy HTTP/3 (QUIC) Standalone FIN Body Validation Bypass Leading to Request Smuggling — CVE-2026-33555
by r3verii · 2026-07-05
- Severity
- High
- CVE
- CVE-2026-33555
- Category
- network
- Affected product
- HAProxy with HTTP/3 (QUIC) support built (USE_QUIC=1); tested on HAProxy 3.0.18
- Affected versions
- HAProxy 3.0.18 (QUIC-enabled builds); requires http-reuse always backend configuration
- Disclosed
- 2026-07-05
- Patch status
- unpatched
References
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-04 |
| Author / Researcher | r3verii |
| CVE / Advisory | CVE-2026-33555 |
| Category | network |
| Severity | High |
| CVSS Score | Not specified in source |
| Status | PoC |
| Tags | haproxy, http3, quic, request-smuggling, http-reuse, content-length, reverse-proxy, cross-user |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | HAProxy with HTTP/3 (QUIC) support built (USE_QUIC=1); tested on HAProxy 3.0.18 |
| Versions Affected | HAProxy 3.0.18 (QUIC-enabled builds); requires http-reuse always backend configuration |
| Language / Platform | PoC client in Python (aioquic); target is HAProxy + nginx backend, run via Docker Compose |
| Authentication Required | No |
| Network Access Required | Yes |
Summary
HAProxy’s HTTP/3 frontend does not validate that a request’s declared Content-Length matches the number of body bytes actually delivered over the QUIC stream before the stream is closed (a “standalone FIN”). When HAProxy translates such a malformed HTTP/3 request into HTTP/1.1 for the backend, it forwards the (incorrect) Content-Length header while transmitting zero actual body bytes. If the backend answers early (e.g. with a 301 redirect) and then drains the declared-but-missing body bytes from the pooled TCP connection — a pattern enabled by the non-default but common http-reuse always setting — it inadvertently consumes bytes belonging to the next, unrelated HTTP request queued on that same reused connection. Because HAProxy pools backend connections across different client requests, this effectively smuggles one user’s request into another user’s response, a classic cross-user HTTP request smuggling condition. The PoC ships a full Docker Compose lab (HAProxy H3/QUIC frontend, stock nginx backend, and a Python/aioquic attacker client) that continuously sends malformed “poison” requests and demonstrates that a legitimate victim on a separate QUIC connection receives corrupted (400) responses while the attack is running.
Vulnerability Details
Root Cause
HAProxy’s HTTP/3/QUIC request parser does not verify that the actual bytes received on a QUIC stream (terminated by a stream FIN) match the request’s declared Content-Length before proxying the request to an HTTP/1.1 backend over a pooled/reused connection.
Attack Vector
- Attacker establishes an HTTP/3 (QUIC) connection to the HAProxy frontend and sends a POST request with a
Content-Lengthheader larger than the actual body bytes sent, then closes the stream (FIN) early. - HAProxy forwards the request to the backend over a reused HTTP/1.1 TCP connection, including the false
Content-Lengthbut without the missing body bytes. - The backend (e.g. nginx) responds early (such as a 301 redirect) and then reads/drains the connection expecting the declared body length, consuming bytes that actually belong to the next request queued on that pooled connection.
- A victim’s legitimate request sent over a separate QUIC connection gets its bytes misattributed as the tail of the attacker’s request, causing the victim to receive a corrupted/incorrect response (e.g. HTTP 400) instead of their expected response.
Impact
An unauthenticated network attacker can desynchronize a shared HAProxy backend connection pool, causing cross-user response confusion/corruption for other clients traversing the same proxy — potentially leading to response splitting, cache poisoning, or information disclosure depending on backend behavior.
Environment / Lab Setup
Target: HAProxy 3.0.18 (USE_QUIC=1) fronting stock nginx 1.27, http-reuse always
Attacker: Docker Compose (haproxy, nginx, client containers), Python 3 + aioquic in the client container
Proof of Concept
PoC Script
See
client/poc.py,haproxy/haproxy.cfg,haproxy/Dockerfile,nginx/default.conf, anddocker-compose.ymlin this folder.
| |
The lab spins up an HAProxy H3/QUIC frontend with a pooled HTTP/1.1 backend to nginx, plus a Python/aioquic attacker container. Running poc.py in continuous mode repeatedly sends the malformed Content-Length “poison” request; a --once single-shot mode sends one poison request followed by a victim-style request on a separate QUIC connection and reports whether the victim received a corrupted (400) response instead of the expected 200, confirming request smuggling.
Detection & Indicators of Compromise
Signs of compromise:
- Spikes in HTTP 400 (or otherwise anomalous) responses served to unrelated clients immediately following requests with suspicious/mismatched Content-Length values.
- Backend (nginx) access logs showing early 301/redirect responses on connections later associated with unrelated victim requests.
- QUIC/H3 connection logs showing streams closed (FIN) before the declared body length was fully transmitted.
Remediation
| Action | Detail |
|---|---|
| Primary fix | No vendor patch confirmed as of 2026-07-05 — monitor for advisory; ensure HAProxy validates actual received body length against declared Content-Length before forwarding over reused connections. |
| Interim mitigation | Disable http-reuse always (use safe or never) on H3/QUIC-fronted backends until a fix is available, or disable QUIC/H3 support if not required. |
References
Notes
Mirrored from https://github.com/r3verii/CVE-2026-33555 on 2026-07-05.