HTTP Request Smuggling + IDOR
05/12/2019
HTTP Request Smuggling or HTTP Desync is one of the trendy vulnerabilities of the moment and one of my favorites, because it allows you to greatly increase the severity of most common bugs. Here, in this first of a series of HTTP Request Smuggling chained vulnerabilities I've found, I'll explain how I chained it with a inoffensive IDOR to retrieve some user highly confidential information.
Everything is redacted and highly modified to not disclose this bug bounty program's information.
All started thanks to Burp's Request Smuggler plugin with which I detected a possible vulnerable CL.TE
endpoint.
I like to create my own pocs to confirm if an endpoint is indeed vulnerable, so I used the following crafted request to test this CL.TE
.
POST / HTTP/1.1 Transfer-Encoding: chunked Host: xxx.com Content-Length: 35 Foo: bar 0 GET /admin7 HTTP/1.1 X-Foo: k
What I try to achieve with this request is:
- The front-end uses the
Content-Length
header, forwarding the whole request. - The back-end uses
Transfer-Encoding: chunked
processing only the0
which means end of the request. - The remaining part (
GET /admin7
...) is processed with the next request the back-end receives. (I used/admin7
knowing it returned a302
code, to make it easier to identify).
In the following pictures it's possible to see the expected behavior.
Since the back-end uses Transfer-Encoding: chunked
it will only answer till the 0
returning a 404
(the default code for a POST
to /
) and leaving the remaining part unprocessed.
When the next request arrives (GET /
), that remaining part is processed with it, modifying the other user's request.
This can be achieved with the following Turbo Intruder script.
def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=5, requestsPerConnection=1, resumeSSL=False, timeout=10, pipeline=False, maxRetriesPerRequest=0, engine=Engine.THREADED, ) engine.start() attack = '''POST / HTTP/1.1 Transfer-Encoding: chunked Host: xxx.com Content-Length: 35 Foo: bar 0 GET /admin7 HTTP/1.1 X-Foo: k''' engine.queue(attack) victim = '''GET / HTTP/1.1 Host: xxx.com ''' for i in range(14): engine.queue(victim) time.sleep(0.05) def handleResponse(req, interesting): table.add(req)
We can see how the malicious request is sent first and then, 14 simple GET
to /
which return a 404
.
But since this system is vulnerable, one of those simple GET
was internally modified by our malicious request and returns a different response (the 302
which corresponds to /admin7
).
What I usually try in these situations is changing the Host
header in order to redirect the victim to a different website which will be already considered as a high vulnerability.
POST / HTTP/1.1 Transfer-Encoding: chunked Host: xxx.com Content-Length: 55 Foo: bar 0 POST /admin7 HTTP/1.1 Host: malicious.com Content-Length: 100 kk
Unfortunately this nor any request trying to append the victim's req in the body of my target request didn't work, which made me think that maybe an internal header was being set internally and only requests with that header were processed.
I tried to find an endpoint which would allow me to reflect internal headers, but I couldn't find any, instead I found something better, a hidden swagger with all user endpoint's documentation.
I started testing that API, I was able to create my own user and found that some endpoints were potentially vulnerable to IDOR.
For example, this endpoint (/addCard
) which allowed to add a credit card to your account (in reality it wasn't credit cards, but this is easier to understand). You can see how any authorization header nor session cookie is being used, only the user id is needed (675ygtyt675erp
) to add a new card.
POST /addCard/675ygtyt675erp HTTP/1.1 Host: xx.com Content-Type: application/json Content-Length: 83 {"name": "Name","card": "12345", "exp": "00/00", "cvv": "000"}
But just this behavior shouldn't be considered a real vulnerability, since why would someone add a valid credit card to another user?
Here comes how with HTTP Request Smuggling we can turn this into an IDOR on steroids.
POST / HTTP/1.1 Transfer-Encoding: chunked Host: xxx.com Content-Length: 70 Foo: bar 0 POST /addCard/675ygtyt675erp HTTP/1.1 x-ff: kk
Using that request what I'm doing is modifying the path of the next request and if that next request happens to be an addCard
request, the card will be added to an account under my control (675ygtyt675erp
) being able to retrieve that information.