Skip to content
pwnsy
web-securityadvanced#request-smuggling#web-security#owasp#http#appsec

HTTP Request Smuggling: Front-End/Back-End Desync

How HTTP request smuggling exploits parsing disagreements between front-end and back-end servers, the CL.TE and TE.CL variants, and how to defend.

Modern web traffic almost never reaches an application server directly. It passes through a chain: a CDN, a load balancer, a reverse proxy, then the origin. Each hop parses the HTTP request, decides what it means, and forwards it on. HTTP request smuggling is what happens when two hops in that chain read the same bytes and reach different conclusions about where one request ends and the next one starts. That small disagreement lets an attacker smuggle a hidden request past the front-end and have it attach to somebody else's traffic on the back-end.

It is one of the more demanding web attacks to understand, because nothing is malformed in an obvious way. Every request involved is, on its own, valid HTTP. The vulnerability is in the seam between two parsers.

Why the seam exists

HTTP/1.1 has two ways to say how long a message body is. The Content-Length header gives an exact byte count. The Transfer-Encoding: chunked header says the body arrives in size-prefixed chunks and ends with a zero-size chunk. A well-behaved request uses one or the other. The specification says that if both are present, Transfer-Encoding wins and Content-Length must be ignored.

The problem is that the two servers in front of your application may not agree on that rule, or may disagree about what counts as a valid header. When the front-end measures the body one way and the back-end measures it another, the front-end forwards a stream of bytes that the back-end splits at a different point. The leftover bytes, which the front-end thought were body, become the start of a brand new request the back-end will process on the same connection.

The classic variants

The variants are named for which header each server trusts.

VariantFront-end usesBack-end usesEffect
CL.TEContent-LengthTransfer-EncodingFront-end forwards extra bytes the back-end reads as a new request
TE.CLTransfer-EncodingContent-LengthBack-end stops early, leaving a smuggled prefix for the next request
TE.TETransfer-Encoding (both)Transfer-Encoding (both)One server is tricked by an obfuscated TE header into ignoring it

In a CL.TE attack, the front-end honors Content-Length and passes the whole body along, while the back-end honors Transfer-Encoding: chunked, treats the body as ending at the zero-size chunk, and interprets whatever follows as the beginning of the next request. In a TE.CL attack the roles swap. In a TE.TE attack both servers in principle support chunked encoding, so the attacker obfuscates the Transfer-Encoding header (unusual whitespace, duplicate headers, odd casing) so that one server sees it and the other does not, recreating a CL.TE or TE.CL condition.

The details differ, but the outcome is the same: bytes the front-end believed were part of request A are parsed by the back-end as the opening of request B.

Why it harms other users

A single smuggled request against your own connection would be a curiosity. The danger comes from connection reuse. Back-end connections are pooled and shared across many users to save the cost of setting up new ones. When an attacker smuggles a request prefix, that prefix sits at the front of the back-end's input buffer. The very next request that arrives on that connection, quite possibly a different user's, gets appended to the attacker's prefix. The victim's request is now prepended with attacker-controlled content.

The victim is not the attacker

The defining feature of request smuggling is that the payload lands on someone else's request. Because back-end connections are shared, a smuggled prefix attaches to whichever user's request comes next. That is why the impact scales beyond the attacker's own session and why it is treated as a serious, systemic flaw rather than a self-inflicted one.

What an attacker gains

  • Bypassing front-end security controls. The front-end often enforces access rules, blocks certain paths, or filters requests. A smuggled request reaches the back-end without ever being seen by the front-end, so its controls do not apply. An attacker can reach restricted endpoints the front-end would have blocked.
  • Response queue poisoning and capturing requests. By desyncing the connection, an attacker can cause responses to be served to the wrong requests, capturing another user's request (including their headers and session cookies) or serving them an attacker-influenced response.
  • Session hijacking. Captured requests can carry session tokens, and a poisoned response queue can deliver a victim content that leaks or fixes their session.
  • Feeding web cache poisoning. A smuggled request can steer what a shared cache stores against a normal URL, turning a one-shot desync into a persistent bad response served to many users.
  • Credential and data exposure. Where full requests are captured, anything in them (auth headers, form data, tokens) is exposed to the attacker.

Why it is hard to spot

Every request in a smuggling attack is individually well-formed. Logs on either server show valid HTTP. The symptom is behavioral: intermittent wrong responses, requests that seem to belong to another user, an endpoint that occasionally returns something it should not. Detection typically relies on carefully timed probes that measure whether the back-end waited for bytes that never came, which is why the attack was underappreciated for years before systematic techniques for finding it were published.

How to defend against request smuggling

The root cause is ambiguity, so every effective control removes ambiguity or removes the chance for two parsers to disagree.

  1. Make the front-end normalize and reject ambiguity. Configure the front-end to reject any request that carries both Content-Length and Transfer-Encoding, or that carries a malformed or duplicated Transfer-Encoding. If the message is ambiguous, the correct response is to refuse it, not to guess.
  2. Use the same server software and parsing rules end to end where practical. Two hops that share a parser cannot disagree about where a request ends. Where you cannot unify them, verify that they resolve the length headers identically.
  3. Prefer HTTP/2 all the way to the origin. HTTP/2 carries body length in the protocol framing rather than in ambiguous headers, which removes the CL-versus-TE conflict. The catch is downgrading: a front-end that speaks HTTP/2 to clients but HTTP/1.1 to the back-end can reintroduce the flaw during translation, so the downgrade path must be strict and must not forward attacker-supplied length headers.
  4. Disable connection reuse to the back-end where the risk warrants it. Using a fresh back-end connection per client request removes the shared buffer that lets a smuggled prefix reach another user. It costs performance, so weigh it against the exposure.
  5. Keep front-end and back-end software patched. Many smuggling issues are fixed in specific proxy and server releases. Running current versions closes known parsing discrepancies.
  6. Monitor for desync symptoms. Watch for responses served against the wrong request, unexplained cross-user data, and the timing signatures of desync probes.
Refuse, do not reconcile

The single most important mindset is that ambiguous requests must be rejected, not interpreted. Every smuggling variant depends on two servers each making a reasonable-but-different guess about a message that should never have been accepted. A front-end that returns an error the moment length headers conflict denies the attacker the disagreement the whole attack is built on.

A CL.TE desync, walked through

To make the abstraction concrete, trace what the bytes do in a CL.TE case, where the front-end trusts Content-Length and the back-end trusts Transfer-Encoding. The attacker sends a single request whose headers include both a Content-Length and a Transfer-Encoding: chunked. The body is crafted so that, read as chunked, it terminates early at a zero-size chunk, with extra bytes sitting after that terminator.

The front-end reads Content-Length, sees a body of the stated size, and forwards the entire thing, headers and full body, to the back-end as one request. So far nothing looks wrong. The back-end then reads the same bytes, but it honours Transfer-Encoding: chunked. It consumes chunks until it hits the zero-size chunk that marks the end of the body, and it considers the request complete at that point. The bytes the attacker placed after the terminator were, to the back-end, never part of this request. They stay in the connection's input buffer as the beginning of the next request.

Now the timing matters. The back-end connection is pooled and will serve another request soon. When the next request arrives, whether the attacker's follow-up or a different user's, the back-end prepends the leftover smuggled bytes to it. If the smuggled prefix was a partial request line and headers, the victim's request gets folded into it: the victim's own request line might become a header value, or their session cookie might land inside a body the attacker later reads back. The attacker never touched the victim directly. They simply left bytes in a shared buffer that the victim's request completed.

A TE.CL case runs the mirror image. The front-end honours chunked encoding and forwards what it sees as a complete chunked body, while the back-end honours Content-Length and stops reading at the stated count, leaving the remainder as a new request. TE.TE reaches either of these conditions by obfuscating the Transfer-Encoding header so that exactly one of the two servers fails to recognise it as chunked, which collapses the case back to CL.TE or TE.CL.

Detection signals

Because every individual request is valid, smuggling shows up in behaviour and timing more than in any single malformed message. Concrete signals worth watching for:

  • Responses that do not match the request that received them. A client getting content clearly meant for someone else, or an endpoint returning a body that belongs to a different URL, is the classic symptom of a poisoned response queue.
  • Timing anomalies on crafted probes. A common way to confirm a desync is to send a request whose smuggled portion makes the back-end wait for bytes that never arrive. A response that hangs for a fixed timeout on a specific crafted request, while a control request returns immediately, indicates the back-end parsed a length the front-end did not.
  • Header values that contain fragments of a request line. In logs, a header whose value starts with something like a method and path, or a request whose path field carries trailing header-looking text, can be the fingerprint of one request folded into another.
  • Cross-user data appearing where it should not. Session identifiers, cookies, or authenticated content surfacing in the wrong session point at capture through a shared connection.
  • Front-end and back-end request counts drifting apart. When one request on the wire becomes two on the back-end, aggregate counts diverge over time. A back-end that logs more requests than the front-end forwarded is a structural indicator.
  • Access to endpoints the front-end should have blocked. A back-end log showing successful requests to a path the front-end is configured to reject suggests those requests bypassed the front-end by being smuggled inside another.
Test in a controlled setting only

The timing-based probes that confirm a desync will, if the condition exists, interfere with other users on the shared connection. Run smuggling tests against systems you own or are explicitly authorised to assess, in a controlled window, and prefer techniques that detect the desync without poisoning live traffic. This is a class of finding where careless probing can itself cause an incident.

Request smuggling is one member of a family of attacks that exploit disagreements about the meaning of a message. Placing it next to its neighbours clarifies what is distinct about it.

TechniqueRoot causeWho is harmedTypical fix
HTTP request smugglingTwo servers disagree on where a request endsOther users of a shared back-end connectionReject ambiguous length headers, unify parsing
Web cache poisoningA cache keys on the wrong parts of a requestEveryone served the poisoned cache entryCorrect cache keys, sanitise unkeyed input
Host header injectionThe app trusts an attacker-set Host valueUsers of features built from the HostValidate Host against an allowlist
Response splittingAttacker-controlled data injected into response headersUsers receiving the split responseStrip carriage returns and line feeds from header data
Parameter pollutionDuplicate parameters parsed differently by layersThe application logic that reads themCanonicalise parameters before use

The through-line is inconsistent interpretation, which is exactly why smuggling is catalogued as CWE-444: Inconsistent Interpretation of HTTP Requests. Smuggling is the sharpest of these because the disagreement is about message boundaries themselves, and because connection reuse carries the consequence onto other users. Cache poisoning frequently sits downstream of it: a smuggled request is one of the cleaner ways to plant a malicious entry that a shared cache then serves to many, which links the two techniques in practice.

Common mistakes and misconceptions

Teams reasoning about smuggling tend to trip on a handful of points.

  • "Our WAF inspects every request, so we are covered." A front-end security device only inspects what it parses as a request. The smuggled request is hidden inside the body of a message the device already passed, so it reaches the back-end without inspection. Filtering the visible request does nothing for the one concealed in it.
  • "HTTP/2 makes us immune." HTTP/2 removes the CL-versus-TE ambiguity when it is spoken end to end, because length lives in the protocol framing. The common deployment terminates HTTP/2 at the front-end and speaks HTTP/1.1 to the back-end. That downgrade step re-serialises the request and can reintroduce the exact ambiguity, sometimes carrying attacker-supplied length headers straight into the HTTP/1.1 message.
  • "Only the attacker's own traffic is at risk." The defining trait of smuggling is that the payload lands on the next request over a shared connection, which is very often a different user. This is why it is rated as a serious systemic flaw rather than a self-contained one.
  • "The requests are malformed, so strict parsing already rejects them." Each request is individually well-formed. The flaw is the disagreement between two conforming-looking interpretations, which is why simply validating each message in isolation misses it. The fix is to reject the specific ambiguity of conflicting or duplicated length headers.
  • "Turning off keep-alive to the back-end fixes everything." Using a fresh back-end connection per request removes the shared buffer that carries the prefix to another user, which is a strong mitigation. It does not remove the underlying parsing disagreement, and it carries a real performance cost, so it is a control to weigh rather than a complete cure.

How the attack came into focus

Boundary confusion in HTTP is an old idea, and the conflict between Content-Length and Transfer-Encoding was understood in principle for a long time before it was treated as a front-line risk. For years it was seen as a curiosity, partly because reliably finding the condition across real proxy chains was awkward and partly because every request involved looks valid in isolation. The turning point was the publication of systematic detection techniques, especially timing-based methods that let a tester confirm a desync without needing to see both servers' internals. That work turned a theoretical parsing disagreement into a repeatable, high-impact finding, and it reframed the humble length headers as an attack surface. The move toward HTTP/2 changed the terrain again, closing the classic ambiguity for connections that speak it end to end while opening a fresh seam at the downgrade boundary where HTTP/2 is translated back to HTTP/1.1. The core lesson held throughout: wherever two parsers can disagree about the shape of a message, someone will build an attack in the gap.

Frequently asked questions

What is the difference between request smuggling and response splitting? Response splitting injects carriage-return and line-feed sequences into data that ends up in a response, tricking a client or cache into seeing two responses where the server sent one. Request smuggling works on the request side, exploiting a length disagreement between two servers so that one request on the wire is parsed as two by the back-end. Both are boundary-confusion attacks, but they operate on opposite halves of the exchange.

Does HTTP/2 fully eliminate request smuggling? It eliminates the classic Content-Length versus Transfer-Encoding ambiguity when HTTP/2 is spoken from the client all the way to the origin, because message length is carried in the protocol framing. Most real deployments downgrade to HTTP/1.1 somewhere behind the front-end, and that translation can reintroduce the flaw. HTTP/2 is a strong control when it is end to end and when the downgrade path is strict about not forwarding attacker-supplied length headers.

Why is smuggling considered high severity if each request is valid? Because the consequence lands on other users. Connection reuse means a smuggled prefix attaches to whichever request comes next on that shared back-end connection, letting an attacker capture another user's request, bypass front-end controls, or poison a shared cache. The individual validity of each request is precisely what lets it slip past inspection.

Can a web application firewall stop request smuggling? Only partly, and not by inspection alone. The smuggled request is concealed inside the body of a message the firewall already accepted, so content filtering does not see it. A front-end can help by rejecting the ambiguity that enables the attack, refusing any request that carries both length headers or a malformed Transfer-Encoding, which is a configuration decision rather than a signature match.

What is the single most effective fix? Make the front-end reject ambiguous messages outright. If a request carries both Content-Length and Transfer-Encoding, or a duplicated or malformed Transfer-Encoding, the correct response is an error, not a best-effort interpretation. Removing the ambiguity denies every variant the disagreement it depends on. Unifying the parsing behaviour of the two servers, and moving to HTTP/2 end to end, reinforce that.

How do I safely test for it? Test only systems you own or are explicitly authorised to assess. Prefer detection methods that confirm a desync through timing without poisoning live traffic, run them in a controlled window, and be aware that a genuine desync condition can affect other users of the shared connection. Because of that blast radius, smuggling tests warrant more caution than most web-app probes.

Is disabling keep-alive to the back-end a real fix? It is a meaningful mitigation because a fresh connection per request removes the shared buffer that carries a smuggled prefix onto another user. It does not remove the parsing disagreement itself, and it adds connection-setup overhead that can hurt performance under load. Treat it as one control to weigh against exposure alongside rejecting ambiguity and unifying parsers.

Request smuggling is subtle because it hides in the gap between valid parsers, and it is severe because that gap lets one user's crafted bytes ride into another user's request. Attackers probe proxy chains for exactly these desync conditions. Our Exploit Intelligence dashboard tracks how such infrastructure-level techniques show up in active exploitation. Unify your parsing, reject ambiguous messages outright, and move to HTTP/2 end to end with a disciplined downgrade path, and the seam the attack depends on closes.

Sources & further reading

Sharetwitterlinkedin

Related guides