# SOP Bypass via browser-cache

Keybase's user-lookup API returned private data (email, billing, encrypted PGP key) to authenticated requests and lacked cache-control, letting cross-origin fetch force-cache read it from browser cache.

- Payout: unconfirmed
- Severity: medium
- Program: Keybase
- Credited: Aaron Costello
- Disclosed: 2019-12-24
- Write-up quality: 7/10
- Original: https://enumerated.wordpress.com/2019/12/24/sop-bypass-via-browser-cache
- Library page: https://bugbountylibrary.com/r/blog%3Aenumerated-wordpress-com-2019-12-24-sop-bypass-via-browser-cache
- Tags: domain:web impact:data-leak impact:pii-leak kind:bounty platform:blog sev:medium tech:rest technique:chained vuln:cache-deception vuln:cors vuln:info-disclosure

**Target:** Keybase user lookup API endpoint (/_/api/1.0/user/lookup.json)
**Root cause:** The lookup endpoint returned private fields (email, billing, invite counts, timestamps, encrypted PGP key) when the victim's session cookie was present, and responses carried an ETag but no Cache-Control headers, so the browser cached the private response.
**Steps:**
1. Authenticated victim visits keybase.io so session cookie is set
2. Victim loads attacker page containing `fetch('https://keybase.io/_/api/1.0/user/lookup.json?username={VICTIM}', {method:'GET', cache:'force-cache'})`
3. Browser serves the previously cached authenticated response (with private fields) to the cross-origin fetch
4. Attacker JS reads the response body and exfiltrates email, billing plan, invite counts, timestamps, encrypted PGP key
**How found:** Noticed CORS wildcard on lookup endpoints, then observed that authenticated lookups of one's own username returned private fields (email, billing, invite counts, timestamps, encrypted PGP key). Removing cookies dropped those fields, but the response still had an ETag header, hinting at browser caching; recalled @Bitk_'s fetch force-cache trick to read the cache cross-origin.
**Exploitation:** Attacker hosts a page that issues a cross-origin fetch with cache:'force-cache' to the lookup endpoint for the victim's username; the browser returns the cached authenticated response, and the attacker's script reads and exfiltrates the private JSON.
**Insight:** When an endpoint returns different data based on session cookies but still emits ETag/Last-Modified without Cache-Control: no-store, a cross-origin fetch with cache:'force-cache' can pull the victim's cached authenticated response even under ACAO:* with credentials disabled.
