|
2 | 2 |
|
3 | 3 | {{#include ../../banners/hacktricks-training.md}}
|
4 | 4 |
|
5 |
| -**Check the post [https://portswigger.net/research/http-2-downgrades](https://portswigger.net/research/http-2-downgrades)** |
| 5 | +HTTP/2 is generally considered immune to classic request-smuggling because the length of each DATA frame is explicit. **That protection disappears as soon as a front-end proxy “downgrades” the request to HTTP/1.x before forwarding it to a back-end**. The moment two different parsers (the HTTP/2 front-end and the HTTP/1 back-end) try to agree on where one request ends and the next begins, all the old desync tricks come back – plus a few new ones. |
6 | 6 |
|
7 |
| -{{#include ../../banners/hacktricks-training.md}} |
| 7 | +--- |
| 8 | +## Why downgrades happen |
| 9 | + |
| 10 | +1. Browsers already speak HTTP/2, but much legacy origin infrastructure still only understands HTTP/1.1. |
| 11 | +2. Reverse-proxies (CDNs, WAFs, load-balancers) therefore terminate TLS + HTTP/2 at the edge and **rewrite every request as HTTP/1.1** for the origin. |
| 12 | +3. The translation step has to create *both* `Content-Length` **and/or** `Transfer-Encoding: chunked` headers so that the origin can determine body length. |
| 13 | + |
| 14 | +Whenever the front-end trusts the HTTP/2 frame length **but** the back-end trusts CL or TE, an attacker can force them to disagree. |
| 15 | + |
| 16 | +--- |
| 17 | +## Two dominant primitive classes |
| 18 | + |
| 19 | +| Variant | Front-end length | Back-end length | Typical payload | |
| 20 | +|---------|-----------------|-----------------|-----------------| |
| 21 | +| **H2.TE** | HTTP/2 frame | `Transfer-Encoding: chunked` | Embed an extra chunked message body whose final `0\r\n\r\n` is *not* sent, so the back-end waits for the attacker-supplied “next” request. | |
| 22 | +| **H2.CL** | HTTP/2 frame | `Content-Length` | Send a *smaller* CL than the real body, so the back-end reads past the boundary into the following request. | |
| 23 | + |
| 24 | +> These are identical in spirit to classic TE.CL / CL.TE, just with HTTP/2 replacing one of the parsers. |
| 25 | +
|
| 26 | +--- |
| 27 | +## Identifying a downgrade chain |
| 28 | + |
| 29 | +1. Use **ALPN** in a TLS handshake (`openssl s_client -alpn h2 -connect host:443`) or **curl**: |
| 30 | + ```bash |
| 31 | + curl -v --http2 https://target |
| 32 | + ``` |
| 33 | + If `* Using HTTP2` appears, the edge speaks H2. |
| 34 | +2. Send a deliberately malformed CL/TE request *over* HTTP/2 (Burp Repeater now has a dropdown to force HTTP/2). If the response is an HTTP/1.1 error such as `400 Bad chunk`, you have proof the edge converted the traffic for a HTTP/1 parser downstream. |
| 35 | + |
| 36 | +--- |
| 37 | +## Exploitation workflow (H2.TE example) |
| 38 | + |
| 39 | +```http |
| 40 | +:method: POST |
| 41 | +:path: /login |
| 42 | +:scheme: https |
| 43 | +:authority: example.com |
| 44 | +content-length: 13 # ignored by the edge |
| 45 | +transfer-encoding: chunked |
| 46 | +
|
| 47 | +5;ext=1\r\nHELLO\r\n |
| 48 | +0\r\n\r\nGET /admin HTTP/1.1\r\nHost: internal\r\nX: X |
| 49 | +``` |
| 50 | +1. The **front-end** reads exactly 13 bytes (`HELLO\r\n0\r\n\r\nGE`), thinks the request is finished and forwards that much to the origin. |
| 51 | +2. The **back-end** trusts the TE header, keeps reading until it sees the *second* `0\r\n\r\n`, thereby consuming the prefix of the attacker’s second request (`GET /admin …`). |
| 52 | +3. The remainder (`GET /admin …`) is treated as a *new* request queued behind the victim’s. |
| 53 | + |
| 54 | +Replace the smuggled request with: |
| 55 | +* `POST /api/logout` to force session fixation |
| 56 | +* `GET /users/1234` to steal a victim-specific resource |
8 | 57 |
|
| 58 | +--- |
| 59 | +## h2c smuggling (clear-text upgrades) |
9 | 60 |
|
| 61 | +A 2023 study showed that if a front-end passes the HTTP/1.1 `Upgrade: h2c` header to a back-end that supports clear-text HTTP/2, an attacker can tunnel *raw* HTTP/2 frames through an edge that only validated HTTP/1.1. This bypasses header normalisation, WAF rules and even TLS termination. |
10 | 62 |
|
| 63 | +Key requirements: |
| 64 | +* Edge forwards **both** `Connection: Upgrade` and `Upgrade: h2c` unchanged. |
| 65 | +* Origin increments to HTTP/2 and keeps the connection-reuse semantics that enable request queueing. |
| 66 | + |
| 67 | +Mitigation is simple – strip or hard-code the `Upgrade` header at the edge except for WebSockets. |
| 68 | + |
| 69 | +--- |
| 70 | +## Notable real-world CVEs (2022-2025) |
| 71 | + |
| 72 | +* **CVE-2023-25690** – Apache HTTP Server mod_proxy rewrite rules could be chained for request splitting and smuggling. (fixed in 2.4.56) |
| 73 | +* **CVE-2023-25950** – HAProxy 2.7/2.6 request/response smuggling when HTX parser mishandled pipelined requests. |
| 74 | +* **CVE-2022-41721** – Go `MaxBytesHandler` caused left-over body bytes to be parsed as **HTTP/2** frames, enabling cross-protocol smuggling. |
| 75 | + |
| 76 | +--- |
| 77 | +## Tooling |
| 78 | + |
| 79 | +* **Burp Request Smuggler** – since v1.26 it automatically tests H2.TE/H2.CL and hidden ALPN support. Enable “HTTP/2 probing” in the extension options. |
| 80 | +* **h2cSmuggler** – Python PoC by Bishop Fox to automate the clear-text upgrade attack: |
| 81 | + ```bash |
| 82 | + python3 h2csmuggler.py -u https://target -x 'GET /admin HTTP/1.1\r\nHost: target\r\n\r\n' |
| 83 | + ``` |
| 84 | +* **curl**/`hyper` – crafting manual payloads: `curl --http2-prior-knowledge -X POST --data-binary @payload.raw https://target`. |
| 85 | + |
| 86 | +--- |
| 87 | +## Defensive measures |
| 88 | + |
| 89 | +1. **End-to-end HTTP/2** – eliminate the downgrade translation completely. |
| 90 | +2. **Single source of length truth** – when downgrading, *always* generate a valid `Content-Length` **and** **strip** any user-supplied `Content-Length`/`Transfer-Encoding` headers. |
| 91 | +3. **Normalize before route** – apply header-sanitisation *before* routing/rewrite logic. |
| 92 | +4. **Connection isolation** – do not reuse back-end TCP connections across users; “one request per connection” defeats queue-based exploits. |
| 93 | +5. **Strip `Upgrade` unless WebSocket** – prevents h2c tunnelling. |
| 94 | + |
| 95 | +--- |
| 96 | +## References |
| 97 | + |
| 98 | +* PortSwigger Research – “HTTP/2: The Sequel is Always Worse” <https://portswigger.net/research/http2> |
| 99 | +* Bishop Fox – “h2c Smuggling: request smuggling via HTTP/2 clear-text” <https://bishopfox.com/blog/h2c-smuggling-request> |
| 100 | + |
| 101 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments