HTTP Request Smuggling Between Proxy and Server

HTTP Request Smuggling Between Proxy and Server

HTTP request smuggling shows up on almost every audit checklist for sites running behind a reverse proxy, yet most teams still can’t explain what it actually does to a request pipeline. This article covers how the attack works between a front-end proxy and a back-end server, why it’s still common in 2026 despite being documented since 2005, and what to check on your own stack.

The short version: request smuggling happens when a proxy and the server behind it disagree about where one HTTP request ends and the next one begins. That disagreement lets an attacker sneak a second, hidden request into the same TCP connection – one the proxy never inspected but the server will happily process as if it came from a trusted source.

Why proxies and servers disagree about request boundaries

HTTP/1.1 gives you two ways to tell a server how long a request body is: the Content-Length header, which states an exact byte count, and Transfer-Encoding: chunked, which splits the body into chunks each prefixed by its own length. Both are valid. Both are widely supported. And that’s the entire problem.

When a request arrives with both headers set – or with a malformed version of one of them – the proxy and the origin server each have to pick which one to trust. If they pick differently, they parse the same byte stream into a different number of requests. Nginx might trust Content-Length while an older Apache module trusts Transfer-Encoding, or a load balancer might normalize a header that the app server doesn’t. The RFC (RFC 7230, now folded into RFC 9112) says Transfer-Encoding takes priority when both are present, but plenty of real-world parsers – especially older ones or custom HTTP stacks bolted onto embedded services – don’t implement that correctly.

This produces the three classic attack variants: CL.TE (front end uses Content-Length, back end uses Transfer-Encoding), TE.CL (the reverse), and TE.TE (both use Transfer-Encoding, but one can be tricked into ignoring it via header obfuscation – extra whitespace, unusual casing, or a duplicate header). James Kettle’s 2019 PortSwigger research on this popularized the naming and released the desync attack tooling most pentesters still reference today.

A practical walkthrough of a CL.TE attack

Picture a fairly standard setup: an AWS Application Load Balancer or an Nginx reverse proxy in front of a Node.js app running Express with a slightly older version of a body-parsing middleware. The proxy forwards requests based on Content-Length. The Node service, under specific conditions, still processes Transfer-Encoding for chunked bodies.

An attacker sends a single request with both headers. The Content-Length says the body is short, so the proxy reads exactly that many bytes and forwards the rest of the stream untouched, treating it as leftover data or the start of a new request. The back-end server, working off Transfer-Encoding instead, reads a “0” chunk terminator earlier than the proxy expected and then interprets the remaining bytes as the start of a brand-new HTTP request – one the attacker fully controls and the proxy never separately validated.

That injected fragment lands in the request queue right where the next legitimate visitor’s request would normally begin. If the back end pipelines connections (common with keep-alive to reduce TCP overhead), the attacker’s crafted fragment gets prepended to someone else’s real request. Depending on what the attacker plants there, this leads to cache poisoning, session hijacking by capturing another user’s cookies, bypassing front-end access control rules (WAF rules that only inspect the “outer” request), or reflecting an authenticated response back to the wrong user.

Why WAFs and CDNs don’t fully solve this

A common myth is that putting a WAF or CDN in front of the origin eliminates smuggling risk. It often does the opposite. Cloudflare, Akamai, and Fastly all sit in the request path as another parser with their own interpretation of ambiguous headers – which means you now have three parsers (CDN, load balancer, origin) that all need to agree, not two. Cloudflare patched several smuggling-adjacent desync issues around 2022 and has since hardened its HTTP/1.1 parser to reject ambiguous Content-Length/Transfer-Encoding combinations outright, but that protection depends on the CDN sitting directly in front of origin with no other intermediary layers doing their own normalization first.

HTTP/2 is often cited as immune, and end-to-end HTTP/2 genuinely removes the Content-Length/Transfer-Encoding ambiguity since H2 uses length-prefixed binary frames. But most infrastructure runs HTTP/2 from client to edge and then downgrades to HTTP/1.1 for the edge-to-origin hop. That downgrade point is exactly where “H2.TE” and “H2.CL” smuggling variants live, and it’s a growing share of findings in 2025-2026 bug bounty reports as more origins sit behind H2-terminating CDNs.

Common mistakes teams make when assessing this risk

Three patterns show up repeatedly during audits:

Teams assume that because they run a “modern” stack, both parsers must comply with RFC 9112 identically. In practice, sub-dependencies matter more than the primary framework – an outdated http-parser version pulled in transitively, or a custom health-check proxy someone wrote in Go five years ago and forgot about, can introduce the mismatch even when the main app server is current.

Security teams test smuggling with automated scanners only and stop when nothing fires. Detection tools like Burp Suite’s HTTP Request Smuggler extension are good at flagging timing anomalies, but differential parsing bugs are often environment-specific – they depend on the exact proxy version, keep-alive configuration, and connection reuse pattern in production, which a staging environment with different infra doesn’t always reproduce.

Teams fix the specific payload that was reported and consider the issue closed, without addressing the root cause: two different HTTP parsers in the request path. A seasoned appsec engineer treats a confirmed smuggling finding as a signal to normalize the entire proxy chain, not just patch one endpoint.

What actually reduces the risk

Disable HTTP/1.1 keep-alive reuse between proxy and origin where you can afford the latency cost, forcing a fresh connection per request removes most exploitation paths since there’s no queue for injected requests to sit in. Where keep-alive is required for performance, make sure your proxy strictly rejects requests containing both Content-Length and Transfer-Encoding rather than picking one, this is the single highest-value fix. Nginx has supported this rejection behavior by default since 1.21.1 (July 2021); confirm your version and config didn’t disable it. Also check that Transfer-Encoding values are exact-matched (“chunked”, not “chunked ” or “Chunked”) since obfuscation-based TE.TE attacks rely on the origin parser being lenient about casing or whitespace where the front end is strict.

Ongoing detection matters more than a one-time fix, since a dependency bump or a new microservice added behind the same load balancer can reintroduce the exact same class of mismatch six months later. Regular OWASP-aligned security testing and monitoring for anomalous response splitting or unexpected cache behavior should be part of a standing routine rather than a pre-launch checklist item.

FAQ

Can request smuggling happen without a proxy in front of the server?
No – smuggling specifically requires two systems in the chain that independently parse the same HTTP stream and can disagree about boundaries. A single server terminating connections directly isn’t vulnerable to classic CL.TE/TE.CL smuggling, though it can still have its own request parsing bugs.

Does moving to HTTPS prevent request smuggling?
No. TLS encrypts the connection but doesn’t change how Content-Length and Transfer-Encoding are parsed once decrypted. The vulnerability lives at the HTTP layer, not the transport layer.

Is HTTP request smuggling covered under OWASP Top 10?
It isn’t a standalone category, but it typically falls under A05:2021 (Security Misconfiguration) since it stems from mismatched server configurations, and it can enable A01:2021 (Broken Access Control) once exploited to bypass front-end auth checks.

Request smuggling is a configuration and parsing consistency problem more than a coding bug, which is why it survives so many code reviews unnoticed. The practical takeaway is to treat any HTTP/1.1 hop between two different pieces of infrastructure as a place to verify header-parsing agreement, not assume it – and to re-check that agreement whenever either side of the chain changes versions.