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!
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.
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
[[email protected] ~]$ 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:
At least more or less…
Alright so now that we have a better understanding of the back-end we can start to see a way this could be exploited.
/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.
Making the following request to front-end HAProxy server:
Will be forwarded to the back-end as such:
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:
Gets forwarded to the back-end server as:
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.
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.
Let’s see if we can confirm that it’s possible to make request smuggling on our Bullshit Bingo using the following request:
Because it uses the Content-Length when we have
[\x0b] character in the
Transfer-Encoding header, HAProxy will forward the following to 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:
[[email protected] ~]$ 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:
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:
gunicorn will receive the first request it will proceed the
Transfer-Encoding header, processing the first part:
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:
gunicorn received the 2nd request:
It will actually just get appended to the bytes which remained unprocessed before, resulting in the following request:
gunicorn will simply proceed as a valid request.
Alright, let’s see in practice if we can make it work!
[[email protected] ~]$ 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:
Let’s give it a try:
[[email protected] ~]$ 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
- 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
- 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.
- 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 ;)