Skip to content

maxuru ~ % cat haproxy/request-smuggling

HTTP request smuggling through HAProxy

Severity: criticalApplies to: HAProxy 2.xApplies to: HAProxy 3.x

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.

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:

  1. The attacker sends a request HAProxy reads as one request.
  2. The backend reads the same bytes as one and a bit — the trailing portion sits in its buffer, waiting.
  3. 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.

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.

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.

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.3

Do 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 never