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/)