maxuru ~ % cat haproxy/request-smuggling
HTTP request smuggling through HAProxy
Most services on this site have a threat page called “exposed to the internet.” HAProxy does not, because being exposed to the internet is its job. The exposure that defines it is structural: HAProxy sits between two HTTP parsers, and it is only safe while they agree.
What desync means
Section titled “What desync means”HAProxy reads a request, applies your rules, and forwards it to a backend over a connection that is typically reused for many requests. Both ends must agree on exactly where each request stops and the next begins.
When they don’t, an attacker who controls the tail of one request controls the head of the next. Concretely:
- The attacker sends a request HAProxy reads as one request.
- The backend reads the same bytes as one and a bit — the trailing portion sits in its buffer, waiting.
- The next request down that connection — someone else’s, an ordinary user’s — gets the attacker’s leftover bytes prepended.
That victim’s request is now whatever the attacker chose to leave behind.
Why this is worse than it sounds
Section titled “Why this is worse than it sounds”Every http-request rule you have written is enforced by HAProxy on requests
HAProxy can see. A smuggled request is one HAProxy never parsed as a
request. So:
http-request deny if { path_beg /admin }does not apply to it.- IP allow-lists do not apply to it.
- Authentication enforced at the proxy does not apply to it.
- It does not appear in HAProxy’s access log as a request at all.
The advisory language for CVE-2021-40346 put it precisely: the flaw allowed an
attacker to bypass all configured http-request HAProxy ACLs, and possibly
other ACLs. Smuggling is not one bypass; it is a bypass of the layer that
implements your bypasses.
It keeps happening, and that is the point
Section titled “It keeps happening, and that is the point”Two examples, five years apart, in different parts of the codebase:
CVE-2021-40346 — an integer overflow in htx_add_header. Header name and
value lengths were written into the HTX block info without validating them
against the 255-byte and 1 MB limits, so a crafted oversized header wrapped the
length field. HAProxy’s and the backend’s view of the request diverged.
CVSS 8.6, affecting 2.0 through 2.5.
CVE-2026-33555 — HTTP/3. The standalone-FIN code path closed a stream
without checking that the bytes actually received matched the announced
Content-Length. A request advertising a longer body than it sent, terminated
with an empty FIN frame, was forwarded truncated to an HTTP/1.1 or HTTP/2
backend, which then read the following legitimate request’s bytes as the
remainder of the smuggled body. Fixed in 3.3.6.
Different mechanisms, same class. That recurrence is the useful lesson: this is
not a bug HAProxy has, it is a hazard of what HAProxy is. Protocol translation
between HTTP versions is where it concentrates — both examples involve one
version’s framing rules being reconciled with another’s, and HTTP/2 and HTTP/3
carry length information in a fundamentally different way from HTTP/1.1’s
Content-Length and Transfer-Encoding.
What actually mitigates it
Section titled “What actually mitigates it”Patch, on the LTS branch. This is one of the few controls on this site where
patching genuinely is the answer, because the vulnerable code is HAProxy’s own
parser and no configuration removes it. haproxy -vv, then compare against
the current branches. Both CVEs above were fixed by an upgrade and
by nothing else.
Do not relax parsing. option accept-unsafe-violations-in-http-request —
formerly option accept-invalid-http-request — makes HAProxy tolerate malformed
requests it would otherwise reject. Its old name made it sound like a
compatibility shim; the rename is HAProxy telling you what it costs. Strict
parsing is a smuggling defence. If a client needs this to work, fix the client.
Prefer end-to-end HTTP/2, or terminate cleanly. Fewer translation boundaries
means fewer places for two parsers to disagree. If your backends speak HTTP/2,
proto h2 on the server line avoids a downgrade.
Disable protocols you do not serve. If nothing needs HTTP/3, not offering it removes that parser from your attack surface entirely — and it is how HAProxy itself advised mitigating the 2026 QUIC issues before patches shipped:
global no-quic # HAProxy 3.2 and below # tune.quic.listen off # HAProxy 3.3Do not reuse backend connections across clients if you have a
security-sensitive backend and can afford the cost. Smuggling depends on a
shared connection; http-reuse never removes the channel, at a real performance
price.
backend sensitive http-reuse neverRelated
Section titled “Related”- Handle X-Forwarded-For so it can’t be spoofed — the other way a backend is told something untrue.
- HAProxy reqadd, rspadd and reqirep were removed — what to fix before upgrading.