Post

PortSwiggerlabs: HTTP/2 request smuggling via CRLF injection

PortSwiggerlabs: HTTP/2 request smuggling via CRLF injection

Information :

This lab is vulnerable to request smuggling because the front-end server downgrades HTTP/2 requests and fails to adequately sanitize incoming headers.

To solve the lab, use an HTTP/2-exclusive request smuggling vector to gain access to another user’s account. The victim accesses the home page every 15 seconds.

If you’re not familiar with Burp’s exclusive features for HTTP/2 testing, please refer to the documentation for details on how to use them.

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 our lab :

image

Now first we need to see if we can smuggle a request :

  • 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

We get an error , apparently if we use HTTP/1.1 the front end checks for headers present and will block it in case we had both .

Let’s try HTTP/2 instead :

image

Well it did get through this time , this is bcs with HTTP/2 , it uses a different mechanism to determine the CL , using binary frames .

So the idea here is that we’re going to add a Transfer-Encoding: chunked and we’re hoping that when the request gets downgraded to HTTP/1.1 to communicate with the backend , it will see Transfer-Encoding: chunked and if it follows the RFC it should prefer Transfer Encoding over CL and since we’ve already sent a terminating character, it will end the request and allow us to smuggle a new one .

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

0

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

Now first we send our Attack request :

image

Then we follow it up with a normal one like we always do , and if it worked we should get a 404 :

image

But we keep getting a 200 , which is not what we want .

This is probably due to the fact that when the downgrade is happening , it is stripping away the Transfer-Encoding .

One way around this is to add a header, and for its value it will be equal to X then return line Transfer-Encoding , this way when the downgrade is happening , if the return line isn’t stripped out , it will see it as a new Header , that way we bypass the original filter .

1
2
3
4
5
6
7
8
9
10
11
POST / HTTP/2
Host: 0a3c008703d6fb5280884e47007600d3.web-security-academy.net
Cookie: session=3dybuamJGgbv6aREx6OcuyCsUftDGs1U
Content-Type: application/x-www-form-urlencoded
Test_Header: XXX
Transfer-Encoding: chunked

0

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

image

Now if we send it and send a normal request afterwards :

I tried doing it like this but it didn’t work so i used Inspector to add a new header and for the value we kept the same thing :

1
2
Test_Header: XXX\r\n
Transfer-Encoding: chunked

image

Once we add it , it’s completely normal that we can’t see headers anymore since Burp doesn’t know how to render them in HTTP/2 :

image

Now once we send it , then follow it with a new request :

image

Perfect , we are able to smuggle our request :)

Now this lab , we’re tasked with stealing other user’s request headers .

For this one , we need something that gets rendered to us , in our case it’s the search feature :

image

If we make a search for something it stays in the history and it is rendered back to us .

Now the idea here is to smuggle a search request , make the CL too big that it will contain some of the other user’s request as well as part of the response :

  • First let’s smuggle the search request , this is how the normal search request looks like :

image

We won’t need all headers , just the important one that we usually need and the cookie since it is what gives us the History . And of course we need it to support HTTP/1.1 , which is the case (we can attempt a normal request with HTTP/1.1 and it’ll work without any issues) .

image

Perfect , now let’s smuggle it :

image

Now one thing to note here , we need the CL to be bigger than our actual request , since we’re trying to include some of the other request as part of the response .

First we check how long is an actual normal Request that we’re trying to append :

image

It’s around 1100 , plus the 12 we had earlier , if we do 1100 we should be more than fine . As long as the CL isn’t bigger than the value we should be good otherwise we might get some delays .

image

Now if we send a normal one , we will see that our request gets appended to the smuggled one :

image

We see that we are able to see our Session Cookie , this is bcs we’re the ones who triggered the smuggled request by making a GET request after the Smuggled one .

Now if we just send the attack request , and wait for the user to browse the app , it will trigger the smuggled request , which is a POST request that is rendered to us , but this time instead of returning our latest search alone , it will return it as well as the Headers inside the request of the other user , which will leak his Session Cookie .

image

Now we just send it and wait for the user to browse , should take 15 sec like they said , then we refresh the page .

image

Perfect we got the session cookie , now all we need to do is use the Devtools to inject it in our session to login as that user and solve the lab .

image

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

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