HTTP request smuggling is one of those vulnerability classes that sounds arcane until you see what it actually enables. A researcher recently published a detailed writeup describing an HTTP desync bug in Discord’s media proxy that, at its worst, allowed capturing the media requests of arbitrary other users on the platform. The mechanism is subtle, the impact is severe, and the root cause is a decades-old ambiguity baked into HTTP/1.1 itself.
The Core Ambiguity in HTTP/1.1
HTTP/1.1 gives servers two ways to determine where a request body ends: the Content-Length header, which specifies an exact byte count, and Transfer-Encoding: chunked, which terminates the body with a zero-length chunk. RFC 7230 is explicit that if both headers are present, Transfer-Encoding takes precedence and Content-Length must be ignored. That rule exists precisely to prevent ambiguity.
The problem is not what the spec says. The problem is that real-world proxies and origin servers often parse these headers differently, whether due to implementation bugs, performance shortcuts, or deliberate choices to be lenient with malformed requests. When a front-end proxy and a back-end server disagree about where one request ends and the next begins, an attacker can exploit that gap.
James Kettle at PortSwigger codified this attack class in depth with his HTTP Desync Attacks research, first presented at DEF CON 27. The two canonical variants are:
- CL.TE: The front-end proxy uses
Content-Lengthto forward a fixed number of bytes to the back-end. The back-end usesTransfer-Encoding: chunked. The attacker setsContent-Lengthto cover the entire smuggled payload, but the chunked encoding causes the back-end to treat the tail of that payload as the beginning of the next request. - TE.CL: The reverse. The front-end strips or ignores
Transfer-Encodingand passes everything up to theContent-Lengthboundary. The back-end reads chunked encoding, which the attacker has crafted to terminate early, leaving a poisoned prefix in the connection buffer.
A minimal CL.TE smuggle looks like this:
POST / HTTP/1.1
Host: vulnerable.example.com
Content-Length: 49
Transfer-Encoding: chunked
e
SMUGGLED-PREFIX
0
The front-end proxy reads 49 bytes (the whole body) and forwards the full request. The back-end reads the chunked body, sees the terminal 0\r\n\r\n, and considers the request complete, but the bytes after that zero chunk, the “SMUGGLED-PREFIX”, are already sitting in the TCP connection buffer. When the next user’s request arrives over that same keep-alive connection, the back-end prepends those bytes to it.
What a Media Proxy Gets You
Most desync demonstrations target session hijacking or cache poisoning. The Discord variant is more pointed because the target is a media proxy, and media proxies have a property that makes desync particularly dangerous: they forward requests on behalf of end users, often over persistent pooled connections to upstream storage or CDN origins.
Discord’s media proxy sits between the Discord client and the actual media storage. When you send an image in a channel, the client fetches it through media.discordapp.com or the CDN layer, not directly from storage. That proxy rewrites, validates, and forwards the request, then streams the response back.
In a desync scenario, the attacker’s smuggled prefix gets prepended to another user’s request before it hits the upstream. Depending on what the upstream sees, the attacker can observe or manipulate that forwarded request. In a media proxy context, this means the attacker can potentially capture:
- The URL of the media the victim is fetching, which for private attachments reveals that the victim accessed a specific file
- Request headers the proxy forwards, which may include authentication tokens, signed URL parameters, or session identifiers
- Metadata that, combined with timing, allows correlating platform activity to specific users
The “spying on a whole platform” framing in the title is not hyperbole. If the smuggled prefix is designed to redirect the victim’s request to an attacker-controlled endpoint, every user whose request lands on a poisoned back-end connection sends their media fetch to the attacker first. At scale, and media proxies handle enormous request volumes, that window can capture a meaningful number of real user requests before the connection is cycled.
Why Media Proxies Are High-Value Targets
General-purpose reverse proxies handle diverse traffic, which limits what you can infer from a captured request prefix. A media proxy is more constrained: you know the request is fetching a specific piece of content, you know the URL structure, and you often know which channel or conversation that content belongs to.
Discord attachment URLs contain a channel ID, a message ID, and a filename. A captured media request leaks not just that someone accessed a file, but which conversation they were reading. For private direct messages or restricted channels, this is a significant privacy violation even if the media bytes themselves are not captured.
There is also a second-order risk specific to platforms like Discord. Attachment URLs are typically signed or time-limited, but the signing is done at link-generation time. A captured signed URL may be replayable within its validity window, allowing the attacker to download the actual media content after the fact.
The HTTP/2 Wrinkle
HTTP/2 eliminates the Content-Length vs Transfer-Encoding ambiguity by design. Its binary framing layer defines message boundaries at the protocol level, making classic CL.TE and TE.CL attacks impossible over a pure H2 connection. But most large platforms run HTTP/2 at the edge and downgrade to HTTP/1.1 internally, because many back-end components still do not speak H2.
This downgrade reintroduces the problem. An attacker sends an H2 request with a carefully crafted pseudo-header or body that, when the edge proxy translates it to HTTP/1.1 for the back-end, produces an ambiguous Content-Length / Transfer-Encoding combination. Kettle documented this variant as H2.CL and H2.TE in follow-up research. The practical effect is that platforms that believed they were protected by H2 adoption found themselves still exposed through their H2-to-H1 translation layer.
For a platform as large as Discord, which operates a multi-layer proxy architecture with separate CDN, media processing, and storage tiers, the number of HTTP/1.1 hops between components multiplies the attack surface.
Detecting and Mitigating Desync
PortSwigger’s HTTP Request Smuggler Burp extension automates detection by sending probe requests and observing whether a canary suffix reaches the next connection in the pool. The detection is probabilistic and timing-dependent, which makes it difficult to test comprehensively in production environments without risking interference with real traffic.
Mitigation at the infrastructure level generally takes one of three forms:
- Normalize requests at the edge. Strip or reject any request that contains both
Content-LengthandTransfer-Encoding. This breaks a small number of legitimate edge cases but eliminates the ambiguity that desync relies on. - Disable connection reuse between proxy tiers. If each back-end request gets a fresh connection, a smuggled prefix has nowhere to sit. This carries a significant performance cost at high traffic volumes.
- Switch to HTTP/2 end-to-end. Eliminating HTTP/1.1 between all proxy tiers removes the ambiguity at the protocol level. This is the correct long-term fix but requires all internal components to support H2, which is often a multi-year migration.
For immediate remediation, the most practical option is strict header validation at every proxy boundary: reject requests with both headers present, and ensure that Transfer-Encoding obfuscation variants like Transfer-Encoding: xchunked or Transfer-Encoding: chunked with extra whitespace are either normalized or rejected rather than passed through.
Disclosure and Response
The researcher reported this through Discord’s bug bounty program. The specific timeline and bounty amount are detailed in the original writeup. Discord patched the issue, which is the expected outcome, but the more interesting question is how a production media proxy at this scale was left vulnerable to a well-documented attack class.
The answer is probably the same one that comes up every time a large platform ships a desync bug: these vulnerabilities do not show up in standard application security testing. A web application scanner testing individual endpoints will not find desync because the attack requires controlling the parse state of a shared TCP connection between proxy tiers, not just the content of a single request. It requires knowing that two components disagree about a protocol detail, which means reading the implementation of both. At an organization the size of Discord, with separate teams owning each tier of infrastructure, that kind of cross-layer protocol audit rarely happens on a scheduled basis.
The PortSwigger research page remains the best starting point for understanding the full scope of this vulnerability class. If you run any layered proxy architecture, especially one that downgrades from HTTP/2 to HTTP/1.1 internally, the relevant question is not whether you have audited each component in isolation but whether you have ever tested what happens at the boundaries between them.