diff --git a/src/pentesting-web/http-request-smuggling/README.md b/src/pentesting-web/http-request-smuggling/README.md index 35377ab5bc1..b0d1294eb0f 100644 --- a/src/pentesting-web/http-request-smuggling/README.md +++ b/src/pentesting-web/http-request-smuggling/README.md @@ -229,6 +229,29 @@ This is useful to cause a desync, but it won't have any impact until now. 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)**.[[14]](#references) +#### Emerging trigger families (2026) + +Recent large-scale desync research produced several reusable **non-classic triggers** worth testing in addition to CL.TE / TE.CL / TE.0:[[22]](#references) + +- **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.[[22]](#references) +- **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.[[22]](#references) +- **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.[[22]](#references) +- **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.[[22]](#references) + +Minimal dual-matching probe: + +```http +GET / HTTP/1.1 +Host: target +Content-Length: 28 +Content-Length: 28 + +GET /x HTTP/5.1 +X: X +``` + +A later victim request receiving `505 HTTP Version Not Supported` is a strong sign that the embedded request crossed a boundary.[[22]](#references) + #### Breaking the web server 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**. @@ -322,6 +345,23 @@ Check how this header can help exploiting a http desync in: ../../network-services-pentesting/pentesting-web/special-http-headers.md {{#endref}} +## CRLF-powered request splitting and desynchronization + +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](../crlf-0d-0a.md).[[23]](#references) + +### Detection notes + +- 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`).[[23]](#references) +- 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.[[23]](#references) +- 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.[[23]](#references) + +### Escalation patterns + +- **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.[[23]](#references) +- **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` pattern that consumes the next request on the reused connection.[[23]](#references) +- **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.[[23]](#references) +- **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.[[23]](#references) + ### HTTP Request Smuggling Vulnerability Testing 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. @@ -336,6 +376,23 @@ When testing for request smuggling vulnerabilities by interfering with other req - **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. - **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. +### Generic cross-request contamination detection + +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.[[22]](#references) + +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.[[22]](#references) + +### Clean probes vs dirty probes + +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.[[22]](#references) + +### Protocol-ruler transformation detection + +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.[[22]](#references) + +> [!TIP] +> If you want to automate **trigger generation, permutation, and validation** instead of only manual probing, check [AI-Assisted Fuzzing & Automated Vulnerability Discovery](../../AI/AI-Assisted-Fuzzing-and-Vulnerability-Discovery.md) for the generalized LLM/evaluator patterns behind HTTP Terminator.[[22]](#references) + ## Distinguishing HTTP/1.1 pipelining artifacts vs genuine request smuggling 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.[[10]](#references) @@ -798,6 +855,12 @@ Have you found some HTTP Request Smuggling vulnerability and you don't know how ../http-response-smuggling-desync.md {{#endref}} +#### Dangling-byte Response Queue Poisoning + +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.[[22]](#references) + +For full response-side variants, content-confusion chains, and cache-poisoning escalations, review the dedicated response desync page above.[[22]](#references) + ### Other HTTP Request Smuggling Techniques - Browser HTTP Request Smuggling (Client Side) @@ -1038,6 +1101,8 @@ When reviewing caches, confirm that the key includes at least: - [19] [Twisty Python (Werkzeug HTTP request smuggling write-up)](https://mizu.re/post/twisty-python) - [20] [HTTP Request Smuggling + IDOR (hipotermia)](https://hipotermia.pw/bb/http-desync-idor) - [21] [Account takeover via HTTP Request Smuggling (hipotermia)](https://hipotermia.pw/bb/http-desync-account-takeover) +- [22] [PortSwigger Research - Can AI do novel security research? Meet the HTTP Terminator](https://portswigger.net/research/http-terminator) +- [23] [PortSwigger Research - CRLF-Powered Desync Attacks: Beheading HTTP Streams](https://portswigger.net/research/crlf-powered-desync-attacks) {{#include ../../banners/hacktricks-training.md}}