developer security

Steps to setting up a Content Security Policy

As part of protecting your site and your users you should really consider applying a Content Security Policy (CSP) to your site. Here I detail on how I go about setting one up and the possible tools available to ease your pain.

Shaun Wilde
An anonymous hacker in a mask

As part of protecting your site and your users you should really consider applying a Content Security Policy (CSP) to your site. I am going to describe the path I took to applying a basic CSP to my blog and the tools I used to make my journey less painful.

But first what is a CSP and why should you care?

Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to site defacement, to malware distribution.

"But I am already using the X-Frame-Options and X-XSS-Protection headers so I am protected right?" Probably not any more, modern browsers have dropped support for X-XSS-Protection and deprecated some of the capabilities of X-Frame-Options in favour of a CSP being supplied instead. A CSP is however extremely configurable/flexible and with that comes additional complexity. To help with that there are two headers that we need to be aware of when writing a CSP.

  1. Content-Security-Policy - this is the header we will eventually be using to enforce the policy
  2. Content-Security-Policy-Report-Only - this is the header we will start with, this header checks the policy but does not enforce it, allowing corrections to be made until all issues are rectified.

Both headers will generate messages in the console (Chrome at least) and through the use of the report-uri or report-to directives can send those reports to a 3rd party reporting service.

Getting started

To start building a CSP for a site we can start with a basic reporting header

Content-Security-Policy-Report-Only: "default-src 'none';"

The directive default-src is the final fallback for all of the other directives that are available in a CSP header, by setting it to none will force every possible violation to be reported and will allow full visibility of what interactions the site actually has.

Once the basic header has been applied, a refresh of the site will produce a very noisy report.

Example CSP reporting from Chrome

The number of reported violations may look daunting at first but they can be easily reduced by applying directives that refer to interactions reported.

It is not unusual to see default-src set to self rather than none as this can quickly cut the noise with little effort but it will also hide interactions that you may be unaware of; depending on the security posture of a site this may be okay.

Reducing the noise

Rather than set default-src to self, it is sometimes better for visibility to apply the self option to each of the applicable directives. Doing so will allow the browser to trust the host site for each of the interactions reported; in this case the browser will trust images (PNG, GIF, ...), js (JavaScript) and css (Style sheets) from the host site.

Content-Security-Policy-Report-Only: "default-src 'none'; 
  img-src 'self'; script-src-elem 'self'; style-src-elem 'self'"

A CSP report from Chrome with reduced noise

Now that the self interactions have been dealt with it is now possible to deal with the violations relating to external interactions

This now becomes an iterative process of updating the CSP, applying it and reviewing the results. The report will inform as to which directive is being violated and possible remediation steps.

Content-Security-Policy-Report-Only: "default-src 'none'; img-src 'self'; 
  script-src-elem 'self' cdn.jsdelivr.net netlify-cdp-loader.netlify.app unpkg.com; 
  style-src-elem 'self' cdn.jsdelivr.net; frame-src app.netlify.com"

Another CSP report from Chrome with reduced noise

Unsafe?

Now the only violations being reported are suggesting the unsafe-* keywords as possible solutions. Any use of these keywords should be a temporary measure and plans should be made to replace them with something more secure.

There are several ways to deal with inline scripts and styles as suggested in the report. If there are just a few inline scripts and styles then using the suggested hashes is probably the quickest solution. This however can quickly become unwieldly and perhaps using a nonce is much better, this approach will require some sort of backend processing to ensure nonce(s) are generated and applied on each page impression. For modern static sites without a backend server available to generate a nonce, then extracting these scripts and styles into separate files is probably the better approach.

Content-Security-Policy-Report-Only: "default-src 'none'; img-src 'self'; 
  script-src-elem 'self' 'sha256-bX5JSFYfDe6u21arSDQFhDhQZCpkhyBKkXyrQB1U2rM='
    cdn.jsdelivr.net netlify-cdp-loader.netlify.app unpkg.com; 
  script-src 'unsafe-eval'; style-src-elem 'self' cdn.jsdelivr.net; 
  style-src 'unsafe-hashes' 'sha256-dH+oOZOdDv+MWU0F8bCZOoFHX0jFM4+bwNqOKujbv90='; 
  frame-src app.netlify.com"

Leading to no violation reports being generated. This is just one page and more updates to the CSP will be required as the rest of the site is tested against the applied policy.

Using hashes or nonces is recommended but if the violations are coming from 3rd party scripts then they are much harder to control especially if the styles or scripts are changing regularly. It is not surprising developers get frustrated and resort to just using the unsafe-* keywords and move on.

It is also possible to use the strict-dynamic keyword against the script-src directive, this will usually reduce the number of hashes to be applied against the script-src-elem directive. Unfortunately there does not seem to be an equivalent for style-src.

Note: With browsers that support CSP-2 then unsafe-inline is ignored if a hash or nonce is provided for that directive but it will still allow the site to work with older browsers that only support CSP-1.

Moving from reporting to enforcement

After a number of iterations no more violations should be recorded and the next step is to move to enforcement, but what happens if something is missed and something breaks for visitors to the site. As mentioned earlier there are two reporting directives that can be used to send violation reports to a reporting endpoint. You could create your own if you like but if you needs are light then a 3rd party service will probably suffice.

3rd party services

There are many providers to choose from and all offer similar services and capabilities. Adding a reporting endpoint to use one of these providers is as simple and only requires the use of the report-to or report-uri directives. The report-uri directive, though being deprecated, is still the easiest to configure; if you decide to use this then keep an eye on the browser support.

Content-Security-Policy-Report-Only: "...; report-uri <uri-to-service> ;..."

Here are a couple of suggestions:

Report URI

Report URI has a free offering that will suffice for a single site and get access to the basic reporting features. If a violation is reported it will be viewable hopefully enough information exists in the report to rectify the issue.

A CSP report from Report URI

There is also a wizard capability where the reporting can be used to dynamically build a CSP. It should be noted that, under the free version at least, that the CSP generated default to using unsafe-* keywords rather than gather the hashes as we see in the console. This is because the hashes are not sent as part of the report.

A CSP report but no hash

The hash however is generated by Chrome in the console

A violation report in Chrome with a hash

Using hashes or nonces is recommended but if the violations are coming from 3rd party scripts then they are much harder to control especially if the styles or scripts are changing regularly. It is not surprising developers get frustrated and resort to using the unsafe-* keywords alone and move on.

Csper

Csper is another service that provides a reporting endpoint that you can use to collate reports. It doesn't, at this point, have a free offering but it does have a trial that may be worth investigating. It does however have a free Chrome extension that when loaded will gather the reports and then build a basic policy.

A CSP generated by a chrome extension

It is noted they do not grab/generate the hashes but instead suggest that any inline scripts and styles are rewritten instead; this is not bad advice but not something that can be extended to any 3rd party scripts.

Suggested inline rewrites from the Chrome extension

The extension is useful to create a basic policy but will need tweaking before it can be used in production.

CSP Evaluator

CSP Evaluator is a handy tool that will analyse your Content-Security-Poilicy headers and check for any issues. It will provide suggestions on making your policy backward compatible for browsers that do not support the latest CSP syntax. There is also a Chrome extension that will check the policy on the current site but does not seem to, as yet, offer the backward compatibility suggestions.

Ready to go live?

Once the reports stop being generated and no more violations are being captured, it should be possible to switch the policy from reporting only to enforcement by changing the header from Content-Security-Policy-Report-Only to Content-Security-Policy. Now any violations from this point may cause site failures i.e. scripts will not run or styles may not be applied when expected. If you have a reporting endpoint then review any violations and decide, after repeating of course, if and how they need to be addressed.

Enough about XSS what about clickjacking?

X-Frame-Options is still the old faithful if you want to prevent your site being wrapped in an IFRAME element but if you would like your site to be wrapped but control who is allowed to do so then you should extend the CSP.

The CSP equivalent of X-Frame-Options is the frame-ancestors directive e.g.

Content-Security-Policy: "...; frame-ancestors 'self' ;..."

Afterthought

I believe one of my first attempts at XSS was when I got involved in the 'Bearding Bob' competition in 2002 on the Code Project website. To my total surprise I won, though not sure if it was for my code or my graphic skills; probably not the latter and not convinced about the former either so probably just that I read and followed the rules. The picture of the prize seems to have gone from the site but I still have it.

Tankard with a bearded Bob image

As always, your feedback is welcome and appreciated.

Photo by sebastiaan stam on Unsplash