2

I am not getting any response to any way I try to phrase this question, so I keep trying. I feel I've got to be missing something, but I've searched and searched. Why isn't it obvious? Why is it so hard to get an answer?

We are told we should use a Content Security Policy (CSP) nonce on our websites for inline scripts. The nonce should be randomly generated on the server for each request, and picked up by the website, then included via variable in the script tag. This way, we can guard against hacking.

Great, but I can find no way to do that, and still cache the website! Is there a way? Anybody?

If not, why isn't THAT specifically stated in one of these articles about creating CSP nonces? We need answers!

Thank you for letting me rant a bit. But seriously, what's the answer?

1 Answers1

2

tl;dr: you can keep caching responses with nonces in them like you normally would, and CSP will still provide a strong defense against common XSS attacks.

This is an interesting question: when a page containing CSP nonces gets cached (in a public/back-end cache) that would result in the same nonce being transmitted to multiple users. This sounds bad, because nonces are not supposed to be reused. However, quite often this is not a problem in practice.

Let's say you as an attacker are served the same nonce as your victim on a response to the URL /foo/bar. Now you could use this nonce in your XSS payload. Let's consider two of the most common XSS scenario's:

  • URL parameter based reflected XSS: /foo/bar?payload=... will likely have a different cache key than /foo/bar, therefore causing the user to be delivered a fresh response with a new nonce. The attack fails.
  • Stored XSS in dynamic HTML: your payload is now included when the user loads /foo/bar. However, if the user refreshes and gets a response from the cache, then this won't include your injected XSS payload. Alternatively, if they get a fresh response they will also get a fresh nonce. In either case the attack fails.

Now, this doesn't mean that this situation can never be exploited: for example, if /foo/bar has vulnerable JavaScript that modifies the DOM in a way that the attacker payload can be injected it might possible to use the cached nonce. Think of DOM based XSS attacks via data in the # segment of a URL or in AJAX responses.

It will be much trickier for an attacker to exploit the latter situation though, which may be sufficient since the point of CSP is to just provide defense-in-depth; it can not guarantee immunity for 100% of XSS attacks.

If you still want to defend against this scenario: you can disable public caching for HTML responses. Private/browser caching is still fine, however, and you can also safely keep caching any non-HTML resources.

AardvarkSoup
  • 171
  • 1
  • 4
  • The problem is not really that this "would result in the same nonce being transmitted to multiple users," it's that the cached nonce embedded in the js script tags must match the current nonce the server is supplying to the CSP header on each page load. So, I don't think any public or browser caching can work for html in order for this to work. I think perhaps something like Cloudflare's "edge worker" scripts could be made to work, if done right, but I've tried and failed at that so far. – jamminjames May 27 '21 at 19:36
  • Why would this be an issue? Response headers are cached together with the response body; a cache will never mix up bodies and headers from different responses. So regardless of whether the response comes from a cache or from the origin server, the nonces will stay the same. – AardvarkSoup May 28 '21 at 20:08
  • In practice, they are different. Not sure why. – jamminjames May 30 '21 at 02:35
  • Strange, that's not supposed to happen. And when you turn off caching it all works fine? What kind of caching software/setup are you using? Could there perhaps be an additional cache (in your reverse proxy or application server or such) that stores responses before the Content-Security-Policy header is added to them? – AardvarkSoup May 30 '21 at 06:53
  • Well, the method I am using to randomly generate the nonce on the server for each request on my nginx server is with `fastcgi_param REQUEST_ID $request_id;` in the location block. But in order for that to work, I have to turn off the fastcgi_cache for the php files. I tried using `$skip_cache` to skip caching for just the `$request_id` itself, but can't seem to figure out a way to do that. – jamminjames May 31 '21 at 12:44