DEV Community

Rocky
Rocky

Posted on

The Bug That Hides Between Two Servers That Disagree About HTTP

You're testing a web app that sits behind a reverse proxy, the normal setup: a front-end load balancer or CDN terminates the connection, then forwards the request to an application server behind it. Everything works. Then, intermittently, something doesn't: a response comes back that doesn't match the request you sent, or another tester on the same engagement reports getting a fragment of what looks like your traffic. Nobody touched the session. Nothing crashed. It just happened, once, and then it didn't happen again when you tried to repeat it.

That intermittent, hard-to-repro symptom is usually not a race condition in the application. It's two servers disagreeing about where one HTTP request ends and the next one begins, and it's called HTTP request smuggling.

Here's the mechanic. HTTP/1.1 has two different ways to tell a server how long a request body is: the Content-Length header, a plain byte count, and Transfer-Encoding: chunked, where the body is split into self-terminating chunks ending in a zero-length chunk. The spec says if both headers are present, Content-Length should be ignored. Not every server agrees, and more importantly, the front-end proxy and the back-end application server don't have to agree with each other, because they're usually different pieces of software written by different teams.

That disagreement is the whole attack. In a CL.TE desync, the front-end honors Content-Length and the back-end honors Transfer-Encoding. An attacker sends a request with a small Content-Length but a chunked body. The front-end reads exactly that many bytes, decides the request is complete, and forwards it. The back-end, reading chunked encoding, is still waiting for the terminating zero-length chunk that never came in that request, so it holds the leftover bytes and treats them as the start of the next request on that connection, whatever request comes next, from whoever's connection reuse happens to land there. A TE.CL desync runs the same idea in reverse: the front-end honors Transfer-Encoding and forwards the whole chunked body, but the back-end only reads Content-Length bytes and treats everything after as the beginning of a second, attacker-controlled request smuggled onto the queue.

The reason this matters beyond a protocol curiosity is what it lets an attacker do once it works: prepend a fake request onto the front of someone else's, which can steal session tokens, bypass front-end access controls that only inspect the request the proxy thinks it's forwarding, or poison a connection so the next real user's request gets answered with attacker-chosen content. None of this requires a vulnerability in the application's code. It requires two boxes in the request path that parse HTTP slightly differently, which describes most production architectures with a CDN or load balancer in front of an app server.

Finding it starts with a deliberately ambiguous request, one with both headers set inconsistently, sent and timed. If the back-end is left waiting for chunked data that will never arrive, the response hangs until it times out, a detectable signal before you ever try to weaponize it (this is the standard technique documented by PortSwigger's HTTP request smuggling research). The same class of bug has a newer edge-case cousin too: proxies that downgrade HTTP/2 to HTTP/1.1 before forwarding to the back-end can introduce the identical CL.TE-style disagreement even when the original request never had conflicting headers, because the downgrade step is where the ambiguity gets introduced.

None of this is guessable from a scanner report. You have to actually know what Content-Length and Transfer-Encoding mean at the wire level, what a chunked body looks like on the wire, and where HTTP/1.1, TLS termination and HTTP/2 downgrading interact, to recognize the setup instead of just the symptom. That's protocol literacy, and it's exactly the layer the HTTP Protocol Book for Developers is built around, from headers and status codes through TLS and the HTTP/2/3 changes that keep reshaping this attack surface: https://resources.codelivly.com/product/mastering-http-the-backbone-of-the-web/

If you want the hands-on version before the reading, the free HTTP Headers CTF challenge on codelivly.com is the direct warm-up for the header-parsing instincts this whole bug class depends on: https://codelivly.com/ctf/challenges/http-headers

Top comments (0)