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

chrome-cache-to-xss.md (6491B)


      1 ---
      2 title: "Chrome Cache to XSS"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xss-cross-site-scripting/chrome-cache-to-xss.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/chrome-cache-to-xss.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Chrome Cache to XSS
     14 
     15 This is a **browser-local cache abuse** technique in Chrome: you first make the victim cache attacker-influenced content in their own browser, and later force a **history navigation** that restores the bytes from **disk cache** in a more dangerous context. This is **not** the same as CDN/server-side cache poisoning; for shared-cache bugs check [Cache Poisoning and Cache Deception](/hacktricks/pentesting-web/cache-deception/overview).
     16 
     17 The original SECCON challenge writeup explains the chain in depth.<sup>[[1]](#references)</sup>
     18 
     19 The technique revolves around the interaction of two cache types:
     20 
     21 - The **back/forward cache (bfcache)** stores a full page snapshot, including DOM and JavaScript heap.
     22 - The **disk cache** stores fetched responses/resources, but **not** the JavaScript heap.
     23 
     24 For history navigations, **bfcache wins if available**. Therefore, a successful cache-to-XSS chain usually needs to **prevent or evict bfcache** so Chrome falls back to **disk cache**.<sup>[[1]](#references)</sup>
     25 
     26 ### Key Points
     27 
     28 - **bfcache** has precedence over disk cache during back/forward navigation.
     29 - **Disk cache** can store responses retrieved via normal navigations and also resources fetched via `fetch`/XHR.
     30 - Disk cache alone does **not** magically create XSS; it usually needs to be chained with a second primitive such as **HTML injection**, **CSP bypass**, **path traversal / alternate render modes**, **JSONP**, or any response-mode discrepancy that makes the cached bytes later execute/render as a document.
     31 - Because this is **per-browser-state**, the attacker usually needs the victim to **prime their own cache first**.
     32 
     33 ### Disabling / Evicting bfcache
     34 
     35 The classic trick is to keep a live `window.opener` relationship using `window.open()`. In Chrome/Chromium this shows up as the **`related-active-contents`** / `RelatedActiveContentsExist` reason, which prevents the page from being restored from bfcache and makes Chrome fall back to disk cache instead.
     36 
     37 Recent research also showed a second practical option: **evict the old entry from bfcache** by navigating through enough additional documents, while leaving the older HTTP response available in disk cache. This is useful when you need the **old cached body** but still want a **fresh execution context** after going back.<sup>[[2]](#references)</sup>
     38 
     39 ### Reproducing the behavior
     40 
     41 1. Visit a webpage, e.g. `https://example.com`.
     42 2. Execute `open("http://spanote.seccon.games:3000/api/token")`, which returns a `500` response.
     43 3. In the newly opened tab, navigate to `http://spanote.seccon.games:3000/`. This causes the response of `http://spanote.seccon.games:3000/api/token` to be kept in **disk cache**.
     44 4. Trigger `history.back()` in the popup/tab.
     45 5. Because bfcache is disabled by the opener relationship, Chrome restores the previous response from **disk cache**, rendering the cached JSON response in the page.
     46 
     47 You can confirm the behavior using:
     48 
     49 - **Network** panel entries marked as served **from disk cache**.
     50 - **Application --> Back/forward cache** in DevTools.
     51 - Chrome's **notRestoredReasons** tooling, which exposes reasons such as `related-active-contents`.
     52 
     53 ### Hunting checklist
     54 
     55 When testing this technique in the wild, look for the following combination:
     56 
     57 1. A way to make the victim cache **attacker-influenced bytes** under a stable URL.
     58 2. A second code path where the **same URL** is later treated as a **document/navigation target**.
     59 3. A reliable way to **disable or evict bfcache**.
     60 4. A final rendering/execution gadget (HTML injection, CSP bypass, JSONP, content-type confusion, alternate response modes, etc.).
     61 
     62 Good targets are APIs or debug endpoints that can be reached through one flow but later revisited as a top-level navigation or iframe navigation.
     63 
     64 ### Recent exploitation notes (2025+)
     65 
     66 - Do **not** assume `Cache-Control: no-store` disables bfcache anymore. Chrome is gradually allowing bfcache for some `no-store` pages when it decides this is safe, so you need to **verify the actual not-restored reason** instead of relying on headers alone.
     67 - Newer Chrome versions also hardened HTTP cache partitioning for some **cross-site top-level navigations**. Older PoCs that depended on cross-site cache reuse may stop working unless you keep the whole chain in the same browsing context family (popup / iframe / same-site navigation) or adapt the priming step.
     68 - A recent offensive variant abused the same disk-cache fallback idea to **reuse a stale CSP nonce**: leak the nonce, change the injected payload, then force **bfcache eviction** so the browser loads the old HTML from disk cache but re-executes attacker-controlled content in the new flow.<sup>[[2]](#references)</sup>
     69 
     70 ### Practical testing tips
     71 
     72 - If `history.back()` is restoring too much state, you are probably hitting **bfcache**, not disk cache.
     73 - If the page comes back with a fresh JS context but old bytes, you are likely in the **disk cache fallback** path you want.
     74 - Verify each step with DevTools instead of assuming browser behavior: Chrome has changed bfcache eligibility and HTTP cache keying over time, so old CTF/browser-bug tricks can be version-sensitive.
     75 
     76 For more details on bfcache and disk cache, see the browser documentation in the references.<sup>[[3]](#references)</sup><sup>[[4]](#references)</sup><sup>[[5]](#references)</sup><sup>[[6]](#references)</sup>
     77 
     78 ## References
     79 
     80 - [1] [SECCON CTF 2022 Quals: Author writeup for spanote](https://blog.arkark.dev/2022/11/18/seccon-en/#web-spanote)
     81 - [2] [Nonce CSP bypass using Disk Cache](https://jorianwoltjer.com/blog/p/research/nonce-csp-bypass-using-disk-cache)
     82 - [3] [web.dev on bfcache](https://web.dev/articles/bfcache)
     83 - [4] [Chrome's bfcache `no-store` changes](https://developer.chrome.com/docs/web-platform/bfcache-ccns)
     84 - [5] [Chrome's `notRestoredReasons` API](https://developer.chrome.com/docs/web-platform/bfcache-notrestoredreasons)
     85 - [6] [Chromium disk cache design](https://www.chromium.org/developers/design-documents/network-stack/disk-cache/)