Blog/Web Security
Web SecurityJuly 23, 2026 · 7 min read

HTTP Request Smuggling: When Two Servers Disagree on Where a Request Ends

Most web apps sit behind a chain: a CDN or reverse proxy in front, an application server behind. Both have to agree on one basic thing — where each HTTP request ends. Request smuggling happens when they don't.

HTTP gives two ways to state a body's length: the Content-Length header (a byte count) and Transfer-Encoding: chunked (length-prefixed chunks ending in a zero-size chunk). If an attacker sends both, and the front-end trusts one while the back-end trusts the other, they parse the request to different endpoints. The bytes one server thinks are still "body" are what the other server reads as the start of the next request — which belongs to some other user sharing that back-end connection.

The two classic desyncs

  • CL.TE — the front-end uses Content-Length, the back-end uses Transfer-Encoding. The front-end forwards the whole body; the back-end stops at the chunked terminator early and treats the remainder as a new request.
  • TE.CL — the reverse: the front-end honors chunked, the back-end honors Content-Length. The attacker sizes the chunk so that leftover bytes are queued for the next request on that connection.

Either way, the attacker's "leftover" bytes get prepended to the next request that flows over the reused connection. That victim request now carries the attacker's smuggled prefix.

What it buys an attacker

  • Bypass front-end controls. Security rules, auth, and WAF checks enforced at the proxy are skipped by the portion of the request the back-end sees but the proxy never inspected.
  • Capture other users' requests. A smuggled prefix can cause the victim's request — including their session cookie and headers — to be reflected back or stored where the attacker can read it.
  • Cache poisoning and request hijacking across users sharing the back-end connection, because the corruption is on the connection, not just the attacker's own request.

The fix: one unambiguous interpretation across the whole chain

Smuggling only exists when two hops disagree. Remove the ambiguity everywhere.

POST /search HTTP/1.1
Host: example.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED=1

# Two length headers on ONE request. A front-end that honors
# Content-Length and a back-end that honors Transfer-Encoding
# disagree on where the body ends — the "SMUGGLED=1" bytes get
# prepended to the next request on the connection.
#
# Defense rule: a request containing BOTH headers is malformed.
# Reject it outright (400) at the very edge.

The principles

  • Never allow both length headers. A request with Content-Length and Transfer-Encoding is malformed — reject it at the edge with a 400.
  • Normalize framing at the front door. Strip or canonicalize ambiguous and obfuscated Transfer-Encoding values before anything is proxied downstream.
  • Go HTTP/2 end to end. Unambiguous framing removes the entire class of CL/TE desync.
  • Patch the chain. Smuggling is largely a parser-discrepancy bug; keeping proxies and app servers current closes known gaps.

The one line: request smuggling is two servers disagreeing about where a request ends — make the whole chain agree, and reject anything ambiguous at the edge.

Try it in the request smuggling simulation: craft a CL.TE payload, watch a hidden request prepend onto the next one, then apply edge validation and see the desync rejected. It shares DNA with web cache poisoning — both abuse how intermediaries handle your requests.

Share this post

Frequently Asked Questions

Related posts

Race Conditions in Web Apps: When Two Requests Beat Your Check

Check-then-act logic that looks correct on paper breaks when two requests run at once. Here's how TOCTOU race conditions drain balances and reuse one-time codes, and how atomic operations and locks fix them.

Aug 11, 2026 · 7 min read

Mass Assignment: How role=admin Ends Up in Your Update

Mass assignment lets an attacker set fields you never meant to expose — like isAdmin or accountBalance — by adding them to a request body. Here's why binding whole objects is dangerous and how to allowlist fields in Node, Rails, and Django.

Aug 10, 2026 · 6 min read

Insecure Deserialization: How Loading Data Becomes Running Code

Deserializing untrusted data can execute code before your app reads a single field. Here's how gadget chains work in Python pickle, Java, PHP, and Node, and why JSON with a schema is the fix.

Aug 9, 2026 · 7 min read