But this also means that a web page is cached. And if your website has a nonce-based CSP, then your CSP will also be cached. This means, no matter how random the nonce is (and you serve different nonces for every request), as long as Service Worker sees that the request is same, it'll respond with cached content, which always have the same CSP nonce.
To see if this can be exploited, I made a CSP bypass challenge.
Above page uses Strict CSP, and Service Worker code was taken from Google's SW intro page (second example you see when you click the link).
So it should be safe against XSS bugs, right? :)
Well, challenge was made in a way that it's possible to bypass Strict CSP, and I'm hoping that people will find this CSP bypass in real websites someday :)
The challenge has 2 injection points.
- location.hash (Service Worker doesn't see the hash)
- Referrer passed to server (Service Worker doesn't see this either)
There are many other sources of XSS that Service Worker doesn't use as a key for a request (e.g. Stored XSS payload can't be keyed either).
Intended solution was following.
Gareth wrote a great post about leaking information using <base> tag's target attribute even under Strict CSP. I used similar trick, which is iframe's name. I used referrer to inject iframe and name attribute leaked nonce of the legit script tag, and simply used a leaked nonce to execute script, through location.hash. This is possible because Service Worker doesn't care about changes in location.hash so it'll still serve cached content.
On the other hand, @lbherrera_ solved the challenge using CSS.
He used referrer to inject <input> tag and set nonce as a value, and then brute-forced nonce character one by one using CSS. When when brute-force identifies a character, it'll send a request to his server, which will set the cookie with a matched nonce character, and save whole nonce this way. After whole nonce is stolen, he would use the location.hash to perform XSS with proper nonce.
- Service Worker might help bypass nonce-based CSP
- Always fix XSS bugs even if XSS is blocked by CSP. Time to time, I find CSP bypass in the browser as well (e.g. this). All mitigations have bypasses :)
After publishing this post, I saw some comments that are worth noting, so I'm sharing it here.
@SecurityMB shared a solution by taking over valid nonce. This is possible in non-chromium browsers because they don't have nonce-hiding protection in the browser. This also means that one DOM-based XSS that can't be keyed by Service Worker is enough to bypass nonce-based CSP because you can use recursive CSS import to leak the nonce. Check out ONSEN tool by @mage_1868 for example.
@we1x shared his opinion that any form of caching allows extraction of nonces. This gave me an idea that Signed HTTP Exchanges (i.e. SXG files) is actually not compatible with nonce-based CSP by design. Because it's a file that stores response headers as well as response body for maximum 7 days (where DOM-based XSS could still occur). Which I've filed an issue and looking forward to a solution :)