Challenge Overview#
- Challenge name: 0.CL request smuggling
- Category: HTTP Request Smuggling
- Level: Expert
- Objective: Carlos visits the homepage every five seconds. Exploit 0.CL request smuggling to execute
alert()in his browser.
1. Fundamental Concepts#
1.1 The Inherent Architectural Flaw of HTTP/1.1 Message Framing#
HTTP/1.1 is an ancient, ASCII-based, stream-oriented protocol. On persistent TCP/TLS connections (HTTP Keep-Alive), multiple sequential HTTP requests and responses are transmitted down a single shared socket stream with no explicit frame delimiters. A server determines where one request ends and the next begins strictly based on framing metadata:
Content-Length(CL): Explicit byte count of the body.Transfer-Encoding: chunked(TE): Dynamic chunk frames terminated by a zero-length chunk (0\r\n\r\n).- Implicit Zero (0): Methods or status codes where no body is permitted by specification or server configuration (e.g., standard
GET,HEAD, or static asset lookups), causing the server to assume the body length is 0 immediately following the header block delimiter (\r\n\r\n).
When a reverse proxy (front-end) and an application server (back-end) disagree on which framing mechanism takes precedence, an HTTP Desynchronization vulnerability is born.
1.2 Defining the 0.CL Desynchronization Condition#
In standard request smuggling nomenclature:
$$\mathbf{[Front\text{-}End\ Interpretation]\ .\ [Back\text{-}End\ Interpretation]}$$- In
CL.0(2022): The front-end readsContent-Length: Xand pumps $X$ body bytes to the back-end. The back-end ignores the body (treating length as 0) and processes only the headers, leaving the unread $X$ body bytes sitting in the back-end socket buffer for the next request. - In
0.CL(2025): The front-end ignores theContent-Lengthheader (treating body length as 0), forwarding only the header block to the back-end. The back-end processes theContent-Length: Xheader and suspends request execution while waiting to consume $X$ bytes of body from the socket.
The 0.CL condition typically arises through Header-Value (H-V) Parser Discrepancies, such as inserting whitespace before the colon:
| |
Strict front-end proxies consider a header with a space before the colon malformed and silently discard or ignore it (defaulting to body length 0). Permissive back-end parsers strip the trailing space, recognize the valid header, and await 50 body bytes.
1.3 The “0.CL Deadlock” Dilemma#
Historically, 0.CL was dismissed as a security dead end because of the Upstream Connection Deadlock:
| |
Because the front-end waits for the back-end’s response before streaming subsequent data, and the back-end refuses to respond until it receives the declared body bytes, the connection enters an inescapable stall until a network timeout fires.
1.4 The Breakthrough: Early-Response Gadgets (ERG)#
An Early-Response Gadget (ERG) is an endpoint or condition that forces the back-end server to generate and transmit an immediate HTTP response before reading the request body, while crucially leaving the persistent TCP connection open (Keep-Alive).
- On Windows/IIS: Requesting DOS reserved device names (e.g.,
/con,/aux,/nul,/prn) causes the operating system filesystem driver to trigger an immediate exception. IIS catches this and returns an instant response (such as200 OKor400 Bad Request) without reading the body, leaving the socket intact. - On Reverse Proxies / Directory Handlers: Requesting unslashed directory paths or static resource handlers with non-standard methods (e.g.,
POST /resources/css/anything) triggers an immediate redirect or static error handler that responds instantly without consuming the body.
When an ERG is triggered with 0.CL:
- The back-end responds immediately to Request 1, satisfying the front-end.
- The front-end closes Request 1’s cycle and allows the next request on the persistent connection.
- The Trap is Set: The back-end’s socket parser is still in a deficit state: it expects $X$ bytes of body for Request 1. When the next request arrives down that socket, the back-end slices off and consumes the first $X$ bytes of the new request as the missing body of the previous request!
1.5 The Double-Desync Concept: Weaponizing 0.CL into CL.0#
Slicing bytes off an incoming request usually only corrupts its headers, producing a 400 Bad Request (“Mystery 400”). An attacker cannot force a victim to prepend a malicious payload to their own request.
To convert a byte-slicing condition into an exploit that hijacks other users, the attacker executes a Double-Desync:
- Stage 1 (0.CL via ERG): An ERG request sets a trap on the back-end socket, priming it to swallow a specific number of bytes ($N$).
- Stage 2 (Chopped & Weaponized into CL.0): The attacker immediately sends a multi-part request. The back-end swallows the first $N$ bytes (
stage2_chopped), which shears off the outer request line. The remainder of the request (stage2_revealed+smuggled) is read as a legitimate request followed by an unconsumed payload left behind in the buffer—effectively converting the socket state into aCL.0desynchronization! - Stage 3 (Victim Request): When a victim (Carlos) visits the application, their incoming request is appended directly to the smuggled payload waiting in the buffer, executing the malicious action.
2. Attack Model & Architecture#
2.1 Architectural Topology#
The target environment comprises:
- Attacker (Turbo Intruder): Emits precision pipelined requests across raw TCP streams.
- Front-End Reverse Proxy: Normalizes traffic, distributes incoming requests across persistent back-end socket pools, ignores
Content-Length : %s(space before colon). - Back-End Application Server: Parses
Content-Length : %s, hosts the blog application, and maintains directory handlers. - Victim Client (Carlos): An automated headless browser bot visiting the homepage (
GET /) once every 5 seconds.
| |
2.2 Attack Workflow & Data Flow Diagram#
| |
3. Step-by-Step Exploitation (PoC)#
3.1. Phase 1: Application Reconnaissance & Baseline Inspection#
We initiate reconnaissance by capturing a baseline request to the target blog application. As shown in Figure 1, requesting the root directory returns a standard HTTP/2 200 OK response with blog posts:

3.2. Phase 2: Uncovering the Reflected XSS Sink in User-Agent#
To solve the lab, we must force the victim (Carlos) to execute alert(). Since we cannot inject arbitrary parameters into Carlos’s standard GET / request directly, we need a reflection point where an attacker-controlled header alters the HTML output.
Auditing the blog posts (specifically post ID 10), we inspect the comment submission form:

In Figure 2, line 82 reveals that the server reflects the client’s User-Agent header into the value attribute of a hidden input named userAgent:
| |
We test for attribute escape by modifying the header in Burp Repeater:
| |

As highlighted in Figure 3, the server reflects our payload without output sanitization or HTML entity encoding:
| |
The string breaks out of the input tag and injects an active <script>alert(1)</script> tag. We now have our weaponized payload target: GET /post?postId=10.
3.3. Phase 3: Discovering & Verifying the Early-Response Gadget (ERG)#
Next, we probe for an Early-Response Gadget capable of surviving 0.CL desynchronization. We test sending a POST request to the static stylesheet directory /resources/css/anything with an obfuscated Content-Length header containing a space before the colon (Content-Length : 50):

In Figure 4, the back-end server immediately issues an HTTP/2 302 Found redirection to /resources/css/anything/ with Content-Length: 0. It does not stall or wait for the declared 50 bytes of body. Furthermore, the connection remains open.
When sending a subsequent request manually in Repeater (Figure 5), it returns 200 OK because Repeater dispatches requests over independent HTTP/2 streams rather than raw, pipelined HTTP/1.1 TCP connections:

3.4. Phase 4: Weaponization via Turbo Intruder (The Double-Desync PoC)#
To achieve continuous stream alignment and catch Carlos during his 5-second visit cycle, we route the request to Turbo Intruder (Figure 6):

We implement the complete exploit script tailored to our target parameters:
| |
Line-by-Line Breakdown of the 0.CL Turbo Intruder Script:#
concurrentConnections=10&engine=Engine.BURP: Opens a pool of 10 persistent TCP sockets managed via Burp’s internal engine, ensuring sufficient connection coverage across the back-end pool to intercept Carlos.stage1: Implements the Early-Response Gadget. NoteContent-Length : %s(space before colon). The length is dynamically set tolen(stage2_chopped).stage2_chopped: The sacrificial block. When Stage 2 is sent down the socket, the back-end swallows exactlylen(stage2_chopped)bytes to fulfill Stage 1’s deficit.stage2_revealed+smuggled: What remains afterstage2_choppedis swallowed.stage2_revealed(GET /404) is executed by the back-end, leavingsmuggled(GET /post?postId=10with the XSS payload) stranded at the head of the socket buffer—achieving CL.0 desync!engine.queue(victim): A local probe that simulates a victim request. When Carlos’s real request hits the primed socket, Carlos executesalert(1), and our subsequent probe detects the solved banner, cleanly canceling the attack loop viareq.engine.cancel().
3.5. Phase 5: Technical Evidence of Socket Desynchronization#
Running the attack produces clear forensic evidence of 0.CL desynchronization across the socket pool:
Evidence 1: The “Mystery 400” (Row 18)#

In Figure 7, Row 18 is a clean, perfectly formed request (GET / HTTP/1.1). Under normal circumstances, it must return 200 OK. However, because it arrived on a socket where stage1 and stage2 had desynchronized the byte stream, the back-end swallowed the first few characters of the request line (reading something like ET / HTTP/1.1). This syntax violation caused the parser to reject it with 400 Bad Request. This is concrete proof of active socket byte-slicing.
Evidence 2: Sliced Request Structure (Row 86)#

In Figure 8, inspecting Row 86 exposes the exact mechanism of the Double-Desync:
- The initial request headers (
OPTIONS / HTTP/1.1 ...) were completely consumed by Stage 1. - The remaining byte fragment (
X: Y) was concatenated directly with the next request line (GET /404 HTTP/1.1), producingX: YGET /404 HTTP/1.1. - Directly beneath it sits our smuggled payload:
GET /post?postId=10 HTTP/1.1withUser-Agent: a"/><script>alert(1)</script>.
3.6. Phase 6: Capturing the Solved State & Execution Explanation#
Within 51 seconds (after 207 total requests dispatched across the 10 connections), Turbo Intruder transitioned to Cancelled / done.
Inspect Row 206 (Figure 9):

| |
Navigating to the browser confirms that the lab has been successfully solved (Figure 10):

In-Depth Analysis: Why and How the Lab Was Solved#
- The True Victim: Carlos is a background crawler visiting the homepage (
GET /) once every 5 seconds over the shared connection pool. - The Socket Interception: While Turbo Intruder flooded the connection pool with the 3-stage pipeline, one of the shared persistent TCP sockets was primed with the smuggled prefix:
1 2 3 4 5 6GET /post?postId=10 HTTP/1.1 User-Agent: a"/><script>alert(1)</script> Content-Type: application/x-www-form-urlencoded Content-Length: 5 x=1 - Execution in Carlos’s Browser: Carlos’s
GET /request was routed by the front-end to this exact socket. The back-end treated Carlos’s request as the body of our smuggled request. In response, the back-end returned the HTML of/post?postId=10directly to Carlos’s browser session. - Triggering the Alert: Carlos’s browser parsed the returned HTML, encountered the unescaped payload
<script>alert(1)</script>, and executed the script. - Turbo Intruder’s Clean Exit: When Carlos triggered
alert(), the academy platform updated the site state. The very nextvictimrequest sent by Turbo Intruder (Row 206) received the updated HTML containing"Congratulations", satisfying the script’s exit condition and cleanly terminating the attack.
4. Remediation & Defense Strategies#
4.1 The Fundamental Solution: Mandate End-to-End HTTP/2 or HTTP/3#
As demonstrated by James Kettle’s research, attempting to patch text-based framing ambiguities in HTTP/1.1 is an endless game of whack-a-mole (“The Desync Endgame”).
- Binary Framing: In HTTP/2 and HTTP/3, message length is encoded as an unambiguous binary integer within frame headers.
- Stream Multiplexing: Messages are divided into frames assigned to unique Stream IDs. There are no shared raw text streams where one request’s bytes can spill into another.
- Prohibit HTTP/2 Downgrading: Front-end proxies must maintain HTTP/2 connections all the way to back-end application servers. Translating incoming HTTP/2 requests into HTTP/1.1 reintroduces all framing vulnerabilities.
4.2 Reverse Proxy & WAF Hardening#
1. Strict RFC 9112 Header Parsing & Normalization#
Enforce strict validation of whitespace in header fields. In accordance with RFC 9112 §5.1, no whitespace is permitted between a header field name and the colon. Any request containing whitespace before the colon (such as Content-Length :) must be immediately rejected with an HTTP 400 Bad Request rather than silently ignored or forwarded:
| |
2. Terminate Connections on Unconsumed Body Responses#
Reverse proxies must track declared request lengths against consumed bytes. If a back-end returns a response (especially a 3xx redirect or 4xx error) before consuming the declared Content-Length, the proxy MUST forcefully close the backend TCP socket (Connection: close / TCP RST) rather than returning it to the idle connection pool.
3. Prohibit Shared Connection Pooling Across Distinct Security Contexts#
Do not reuse backend Keep-Alive connections across multiple untrusted clients. If persistent connections are required for performance, partition socket pools by authenticated user session or client IP.
4.3 Web Server Configuration (Apache, IIS, Nginx)#
1. Enforce Connection Closure on Early Errors & Redirects#
Configure web servers so that any early response issued before consuming the request body includes an explicit Connection: close header and terminates the underlying TCP socket:
| |
2. Disable Legacy DOS Device Lookups (IIS)#
On Windows/IIS deployments, ensure that path validation routines reject legacy reserved device names (CON, PRN, AUX, NUL) at the URL rewriting layer before filesystem handlers are invoked.
4.4 Application-Level Defenses#
1. Context-Aware Output Encoding (XSS Mitigation)#
Never reflect untrusted headers directly into HTML attributes without rigorous contextual encoding. The reflected XSS on User-Agent served as the critical exploit payload:
- In PHP / Blade / Twig / React, ensure all reflected attributes are processed through HTML entity encoders (e.g.,
htmlspecialchars($ua, ENT_QUOTES, 'UTF-8')).
2. Content Security Policy (CSP)#
Deploy a restrictive Content Security Policy that forbids inline script execution:
| |
A strong CSP prevents injected <script>alert(1)</script> tags from executing even if an attacker successfully smuggles a reflected XSS payload.
References & Further Reading#
- James Kettle (PortSwigger Research): HTTP/1.1 Must Die: The Desync Endgame (DEF CON 33 / Black Hat USA 2025)
- Brandon T. Elliott: Lab Writeup: PortSwigger - “0.CL Request Smuggling”
- RFC 9112: HTTP/1.1 — Section 5.1 (Field Names) & Section 9.3 (Request Body Handling)
- PortSwigger Web Security Academy: Advanced Request Smuggling
- CWE-444: Inconsistent Interpretation of HTTP Requests (‘HTTP Request Smuggling’)
