The CTF had a web challenge,
uploooadit which I quite liked due to my affection towards the attack of HTTP Desync.
The endpoint was used to save plain-text files to the blob storage. It took
Content-Type: text/plain and a custom header
X-guid containing an
id, an identifier for the files to fetch them later. A valid request looked like this:
Provided the valid
guid, we get to fetch our saved file with this endpoint:
And if we send an invalid request to the invalid endpoint, we reveal the frontend HTTP server being used:
So we have a Frontend Server
haproxy 1.9.10 and a backend app is written in
Flask which is served by the Gunicorn WSGI.
After the usual assessment, the simple scenario and code leaves us with only the situation of testing it for HTTP Desync between HAProxy and Gunicorn.
The Desync can only help us in poisoning the sockets of the backend server, But if we assume that there can be a Bot that is hitting the backend server in intervals with the flag in it’s HTTP request, then the whole scenario starts making sense.
As it turns out, the combination of HAProxy and Gunicorn is Vulnerable to CL-TE HTTP desync, what we mean by that is, we can send the
Transfer-Encoding(TE) together but if we malform the value of
Transfer-Encoding a little bit by pre-pending non-printable character like “\x0b” (vertical tab) or “\x0c” (form feed), HAProxy will ignore the header and give precedence to CL header but when this is passed to Gunicorn it will parse the TE header correctly and give precedence to that, So if we send a Raw Request like following:
As the HAProxy parses
It only considers the CL header and sends the
second request as the body to Gunicorn
But when the Gunicorn parses the TE header, It breaks the above raw request in 2 POST requests.
One of which is normal and complete request till the
0 byte chunk of Transfer-Encoding and where the second request is our poison for the TCP socket which has
Content-Length: 385. The Gunicorn will wait for the next HTTP/TCP packet till the length of 385 is reached.
And as we had assumed, what if we had a Flag BOT which submits HTTP request to the backend after some interval, we can steal its request by making it fall after our poison if it does then the raw HTTP request by the BOT will become our Poison HTTP request’s body and will be perfectly stored through
POST /files endpoint for us to steal through
Let’s check it out :)
The Exploit request with
And we get the the flag 🚩at
HTTP Desync is quite fun and prevalent considering the modern architecture that web apps are built on these days.
It was a fun challenge. Definitely some rabbit holes followed in previous CTFs on HTTP Desync helped me out in solving this one in minutes.
On another note, I play for UnderDawgs, if you are looking for a team and are a nerd for maths, crypto, and reversing, please hit us up.