Response headers – adding Content-Security-Policy

I recently wrote an update as I continue to work on my response headers, in which I said that I was working on adding Content Security Policy (CSP), with the help of Scott Helme, who has written a great blog post on this.  He has also created an excellent site called report-uri.com which has a number of tools, including one to help you build your CSP.

As a refresher, this is used to control what access different content on your site has.  For example, you can control what javascript and stylesheets can be included, inline or from different domains, etc.  This can help to limit the attack surface for things like cross-site scripting attacks.

So there are lots of parts, but here’s how I went about it…

Default Source (default-src)

This is the default setting, so to be really secure, I went with “none”.  This essentially means don’t allow any content at all.  It is then overridden for each content type explicitly.

Script Source (script-src)

This is applied to all the javascript files and script tags on the page.  I started with “self”, as I wanted everything from my site (“www.riklewis.com”) to work.  Then I looked through my code and added in the CDN domains that I was using to load the libraries I’m using… “https://code.jquery.com https://maxcdn.bootstrapcdn.com https://cdnjs.cloudflare.com” (space delimited).

Style Source (style-src)

This is applied to all stylesheet files and style tags on the page.  Again, I started with “self”, and then needed to add “https://maxcdn.bootstrapcdn.com”.

Image Source (image-src)

You guessed it, this is applied to all the images on the page.  This time I needed “self” and also “https://www.gravatar.com”.

Font Source (font-src)

I don’t load any fonts from my own site here, so I just need to add “https://maxcdn.bootstrapcdn.com”.

Connect Source (connect-src)

This is applied to things like AJAX requests, web sockets and event sources.  I don’t have any of these currently, so I’ve left this out, which means it will default to the Default Source, which is “none”.  I could also explicitly set this to “none”.

Media Source (media-src)

This is applied to audio and video tags.  I don’t have any, so I’ve left this out.

Object Source (object-src)

This is applied to object, applet and embed tags.  I don’t have any, so I’ve left this out.

Child Source (child-src) / Frame Source (frame-src – deprecated)

These are applied to frame and iframe tags.  I don’t have any, so I’ve left these both out.  You should use Child Source instead of Frame Source though, as the later is deprecated.

Worker Source (worker-src)

This is applied to things like service workers, which I don’t have, so I’ve left this out.

Frame Ancesters (frame-ancesters)

This is applied to frame and iframe tags, stating which parents may embed a page.  It replaces the “X-Frame-Options” header, but again, I’ve left it out.

Form Action (form-action)

This is applied to form tags, stating which location they can post to.  I don’t have a form, so I’ve left it out.  In case you’ve forgotten, this means it defaults to the “Default Source”, which is “none”.

Upgrade Insecure Requests (upgrade-insecure-requests)

My site currently runs on HTTP (non-secure), so I’ve left this out, but I’ll definitely be looking to add them in moving forwards.

Block All Mixed Content (block-all-mixed-content)

Same as above, I’ve left these out but I’ll be looking to add them in moving forwards.

Reflected Cross-Site Scripting (reflected-xss)

Reflected cross-site scripting is bad!  I’ve set this to “block”.  You can set this to “filter”, which means the browser will try to cleanse the script, but I think it’s safer to block it completely, if XSS is detected.

Manifest Source (manifest-src)

This is applied to manifest files, so I’ve set this to “self”, as I have a manifest for icons.

Plugin Types (plugin-type)

This tells the browser which plugins they can invoke, such as the Adobe PDF Viewer.  As I don’t think my site needs any, I’ve left this out.

Referrer (referrer)

This directive tells the browser when (and when not) to send the referer [sic] header when fetching content from another domain.  In my last blog post I decided to set the “Referrer-Policy” header to be “no-referrer-when-downgrade”, and so I’ve set this directive to match that.

Report URI (report-uri)

This is where the browser will post violation reports to, so you can keep an eye on what content is breaking.  This could be because you’ve mis-configured your policy, or it could be because someone is trying to hack your site!  Scott Helme’s excellent site report-uri.com will allow you to configure a URI which you can set here.

So now it’s ready for testing!

To test, instead of setting “Content-Security-Policy”, set “Content-Security-Policy-Report-Only”.  This means that the browser will report all of the violations, but will continue to load the content anyway, which is perfect until you’re sure it’s right.

This is what I came up with…

default-src 'none'; script-src 'self' https://code.jquery.com https://maxcdn.bootstrapcdn.com https://cdnjs.cloudflare.com; style-src 'self' https://maxcdn.bootstrapcdn.com; img-src 'self' https://www.gravatar.com; font-src https://maxcdn.bootstrapcdn.com; upgrade-insecure-requests; block-all-mixed-content; reflected-xss block; manifest-src 'self'; referrer no-referrer-when-downgrade; report-uri https://rik.report-uri.io/r/default/csp/reportOnly;

I then reloaded my site, checked the console, and saw a number of violations!

This is because I’d forgotten a couple of included scripts that are loaded by Google Analytics.  In fact, they also use an inline script tag, but I really don’t want to add “unsafe-inline” to the Script Source, otherwise all inline scripts could be run.

Luckily you can fix inline script and style tags that you want to allow by calculating a SHA-256 hash of the contents and including this in the Script Source.  And Scott’s got a hash generator tool as well, so that’s easy.

Having fixed these problems, and reloading my site with no violations reported, I’m now ready to start enforcing.  This means that I need to set the “Content-Security-Policy” header, and if you’re using report-uri.com then you need to update your Report URI directive as well.

Note that if you want to support IE11, you also need to set “X-Content-Security-Policy” to the same value.  It’s not supported in older versions of IE at all.

Now that I’ve added these extra headers, you can check out my live report.  Last I checked, I got an A+ 🙂