Breachwire.riscent

Breach report

The XSS that won't die: service-worker cache poisoning in PWAs

PWA & Startup BreachesHigh (persistence)2024-01
The bottom lineA single same-origin XSS in a Progressive Web App can be escalated into a persistent person-in-the-middle over cached content — one that outlives the original XSS fix.
Category
PWA & Startup Breaches
Type
Vulnerability class · Security research
Date
2024-01
Severity
High (persistence)
OWASP
Web A05 Injection (XSS) escalated via service-worker cache
CWE / CVE
CWE-79

What happened

Progressive Web Apps use a Service Worker — a script that sits between the page and the network — together with the Cache API to serve content offline and quickly. Security research (including academic work at swcacheattack.secpriv.wien and PortSwigger's service-worker research) demonstrated that this architecture turns a transient bug into a durable one.

A single same-origin cross-site scripting (XSS) flaw on a PWA can be escalated into persistent compromise: the attacker's script overwrites entries in the Cache API, so the malicious HTML or JavaScript is re-served on every subsequent visit. The result is a person-in-the-middle over cached content that survives the original XSS being fixed, bounded only by the service worker's update window. Related PortSwigger research showed service workers can also be hijacked via DOM clobbering, promoting attacker-controlled code to the worker with origin-wide fetch interception.

Root cause

A service worker is a powerful, origin-scoped proxy that serves whatever is in its cache without integrity checks. Combined with any same-origin XSS, that power becomes persistence — fixing the XSS does not evict the poisoned cache. Chromium's own Service Worker Security FAQ states the trust model plainly: a compromised same-origin page is game-over for the worker.

How it would have been caught

Dynamic testing (OWASP ZAP, Burp Suite) finds the underlying XSS; a Content-Security-Policy audit and a review of exactly what the service worker caches (and whether responses are integrity-checked) surface the escalation path. The reproduction injects a payload that writes to the Cache API, then reloads and checks whether the poisoned response is re-served.

How to prevent it

  • Eliminate same-origin XSS first — it is the precondition — with output encoding and a strict Content-Security-Policy.
  • Integrity-check cached responses; expire and evict the cache aggressively; never derive the service-worker registration URL from clobbering-prone DOM.
  • Serve everything over HTTPS and keep the service-worker scope as small as possible.

The Breachwire test (red → green)

Trigger the XSS, write a poisoned entry to the Cache API, reload, and confirm the malicious response persists (the vector is real). Apply the CSP and cache-integrity controls, then confirm the poisoning no longer persists across reloads, while legitimate offline caching still works.