The lab
here is how portswigger describes it
This lab is vulnerable to pause-based server-side request smuggling. The front-end server streams requests to the back-end, and the back-end server does not close the connection after a timeout on some endpoints.
To solve the lab, identify a pause-based CL.0 desync vector, smuggle a request to the back-end to the admin panel at /admin, then delete the user carlos.
this is the final lab in the request smuggling series and it introduces a genuinely new trigger, time. every other lab created a desync with header tricks, ambiguous lengths, or encodings. this one creates it by pausing partway through sending a request, exploiting how the front end streams bytes to the back end and how the back end mishandles a slow request. it is worth going slowly, so this writeup builds the idea up and then walks the whole attack
The idea#
first, streaming. this front end does not wait to receive our whole request before forwarding it, it streams the bytes on to the back end as they arrive. so if we send our headers and then stop, the back end has already received those headers and is waiting for the rest.
second, the endpoint behaviour. /resources is a directory, and asking for it without a trailing slash makes the server answer with a redirect to /resources/. crucially, that redirect fires off the headers alone, the server does not need or wait for a request body to send a redirect. that is the cl.0 flavour, the endpoint effectively treats the request as having no body and responds right away.
third, the bug that ties it together. the back end here is running an apache version with a known request smuggling flaw, and on some endpoints it does not close the connection after a timeout. so if we send the headers of a POST /resources, then pause, the back end answers the redirect immediately and, because of the bug, leaves the connection open instead of closing it. when we finally send the rest of our request, the bytes we send are treated as a brand new request on that still open connection. whatever we placed in the body becomes a smuggled request.
so pause based smuggling is, send headers, let the redirect fire, keep the connection alive through the bug, then send a hidden request in the delayed body. the pause is the whole trick, it separates our request into two halves that the back end reads as two requests.
Step 1 - Spot the clues#
we intercept a normal request and look at the response headers, and there is Server: Apache/2.4.52.

a quick search shows that apache version has a known request smuggling vulnerability, which is a strong hint about what to try. we also confirm the redirect behaviour, requesting a valid directory without a trailing slash, GET /resources, redirects us to /resources/.

that redirect endpoint is our cl.0 vector, it answers on headers alone without waiting for a body.
Step 2 - Set up turbo intruder#
pausing precisely in the middle of a request is not something repeater does well, so we use turbo intruder, which can pause sending at a chosen marker. we send the request to it with extensions, turbo intruder, send to turbo intruder, then convert the request to a POST, change the Connection header to keep-alive, and add a complete GET /admin request into the body of the main request.

then in the python editor we enter this script.
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=500,
pipeline=False
)
engine.queue(target.req, pauseMarker=['\r\n\r\n'], pauseTime=61000)
engine.queue(target.req)
def handleResponse(req, interesting):
table.add(req)
in plain terms, this sends our request and pauses for 61 seconds right after the \r\n\r\n that ends the headers, then sends the rest. the 61 second pause is the timeout window we are abusing, long enough for the back end to answer the redirect and, thanks to the bug, hold the connection open for our delayed body.
Step 3 - Confirm the desync#
we launch the attack. nothing seems to happen at first, which is expected because of the pause, and after 61 seconds two entries appear in the results table.

the first entry is the POST /resources request, which triggered the redirect to /resources/ as normal. the second entry is the response to our smuggled GET /admin/ request. it only tells us the admin panel is available to local users, but that is enough, it proves our smuggled request reached the back end as a separate request. the pause based cl.0 desync is confirmed.
Step 4 - Reach the admin panel as a local user#
the admin panel is restricted to local users, and a request coming from the back end to itself over localhost counts as local. so we change the Host header on the smuggled second request to localhost and relaunch.
POST /resources HTTP/1.1
Host: your-lab-id.web-security-academy.net
Cookie: session=your-session-cookie
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 41
GET /admin/ HTTP/1.1
Host: localhost

now the smuggled GET /admin/ carries Host: localhost, so the back end treats it as a local request and serves us the admin panel.
Step 5 - Read the delete form#
the admin panel contains a form for deleting a user, and we note three details from it, the form's action /admin/delete, the input name username, and the csrf token.
Step 6 - Smuggle the delete request#
we build the smuggled request to delete carlos, a POST /admin/delete/ carrying the csrf token and the username.
POST /resources HTTP/1.1
Host: your-lab-id.web-security-academy.net
Cookie: session=your-session-cookie
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 164
POST /admin/delete/ HTTP/1.1
Host: localhost
Content-Type: x-www-form-urlencoded
Content-Length: correct-length
csrf=your-csrf-token&username=carlos

there is one more adjustment. our smuggled request now has a body of its own, the csrf and username, so it has its own \r\n\r\n after its headers. with the original pause marker of \r\n\r\n, turbo intruder would pause after that second header block too, breaking the request. so we make the pause marker specific to only the first set of headers.
pauseMarker=['Content-Length: 164\r\n\r\n']
this tells turbo intruder to pause only after the outer request's headers, the ones ending in that exact Content-Length line, and not after the inner smuggled request's headers.
Step 7 - Launch and solve#
we launch the attack, wait the 61 seconds, and the smuggled POST /admin/delete/ runs on the back end as a local admin request, deleting carlos.


with this, we solved the lab!
the whole attack from start to end#
let me retell the whole thing simply, because the timing element makes this one easy to lose track of.
the site has a front end that forwards our request to a back end by streaming the bytes as they come, rather than waiting for the full request. the back end is an old apache with two useful weaknesses, it answers a request to the /resources directory with an instant redirect that does not wait for any request body, and on such endpoints it fails to close the connection after a timeout.
we exploited that by cutting our request in half with a pause. we sent the headers of a POST /resources, and then stopped for 61 seconds. the back end, having received the headers, immediately sent back the redirect, and then just sat there with the connection still open because of the bug. when our pause ended and we sent the rest of the request, the back end had already finished the first request, so it read these new bytes as a completely separate second request. that second request was whatever we had hidden in the body.
we first hid a GET /admin to prove the trick worked, and the back end processed it, replying that admin was for local users only. that told us two things, the smuggling worked, and we just needed our smuggled request to look local. so we set its Host header to localhost, and the back end served us the admin panel, because a request to itself over localhost is trusted as internal.
from the admin panel we read the details of its delete user form, the delete path, the username field, and the csrf token, and we built a smuggled POST /admin/delete/ to remove carlos. the only fiddly bit was that this smuggled request had its own body and therefore its own end of headers marker, so we told turbo intruder to pause only after the outer request's headers, not the inner ones. we launched it, waited the timeout, and carlos was deleted.
the elegant and slightly unsettling part is that nothing about the payload was malformed or ambiguous, the desync came purely from timing, from holding a normal request half sent long enough for the server to mishandle the delay
