HTTP request smuggling is an attack that makes two servers disagree about where a request ends on the same TCP connection, letting an attacker slip part of their request onto the front of the next user's request. Today's web often puts a front-end (CDN, reverse proxy) in front, forwarding requests to a back-end server over a shared connection — and when the two disagree about where a request ends, that gap opens.
Content-Length vs Transfer-Encoding
There are two ways to state a request body's length. Content-Length gives the byte count as a number, while Transfer-Encoding: chunked splits the body into chunks, each prefixed by its size in hex and terminated by a zero-size chunk. The trouble comes when both headers appear on one request. The standard (RFC 9112) says to prefer Transfer-Encoding and ignore Content-Length, but not every real-world server does the same. When the front-end trusts Content-Length while the back-end trusts Transfer-Encoding — their interpretations diverge — and the request boundary slips.
CL.TE / TE.CL / TE.TE
This parsing mismatch splits into three classic variants depending on which server uses which header.
- CL.TE: the front-end uses Content-Length, the back-end uses Transfer-Encoding. The front-end forwards the whole body as one, but the back-end parses it as chunked and leaves part behind as 'the start of the next request.'
- TE.CL: the reverse — front-end uses
Transfer-Encoding, back-end usesContent-Length. - TE.TE: both support
Transfer-Encoding, but one is tricked into ignoring it by an obfuscated header (e.g.,Transfer-Encoding : chunked, with spacing/typos/duplication), forcing a mismatch.
POST / HTTP/1.1
Host: victim.example
Content-Length: 13
Transfer-Encoding: chunked
0
GPOST / HTTP/1.1
Host: victim.example
...
In this CL.TE example the front-end reads Content-Length: 13 and forwards everything up to 0\r\n\r\nGPOST... as one request to the back-end. But the back-end follows Transfer-Encoding: chunked, sees the request end at the 0 chunk, and treats the leftover GPOST / HTTP/1.1 ... as the beginning of the next request. That leftover fragment is the 'smuggled' request.
Front-end / back-end desync
That leftover fragment sits in the back-end's connection buffer and prepends itself to the next legitimate user's request that arrives on the same connection. The victim's request is then processed mixed with the path and headers the attacker planted (request desync). Because the front-end and back-end have lost their 'agreement' about the request stream, the divergence can cascade for as long as the connection stays alive.
Impact
The impact is severe. An attacker can bypass front-end access control (WAF, auth) to reach forbidden back-end paths, hijack another user's request to steal sessions or credentials, scramble responses so a victim sees the wrong page (response queue poisoning), or poison a cache to spread malicious content widely. A single parsing mismatch can collapse several defenses — authentication, isolation, and caching — at once.
Defenses
- Reject ambiguous requests: if a request carries both
Content-LengthandTransfer-Encoding, or has malformed chunk syntax, don't process it — reject with400. - Normalize: have the front-end rewrite headers into one canonical form before forwarding, so front-end and back-end are forced to interpret identically, catching obfuscated
Transfer-Encodingvariants. - End-to-end HTTP/2: HTTP/2 states body length via its frame structure, eliminating this ambiguity at the source. But if the front-end downgrades to HTTP/1.1 to reach the back-end, the flaw returns — so keep HTTP/2 all the way to the back-end.
- Audit connection reuse: understand keep-alive and reuse behavior on the connection to the back-end, and standardize on parser implementations that agree.