CSP Practical Example


The Content Security Policy or CSP is an HTTP header where you can specify the domains that the browser should consider to be valid sources.

Read: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

How to configure the CSP header in your server?

This is very easy if you are using Webinoly, just go to the Configuration File /opt/webinoly/webinoly.conf and you will find the option.

The not so easy part is how to configure the right or most convenient value for this header.

Step 1: Set an initial default value.

Content-Security-Policy: default-src 'self'

This will allow only resources from the same site domain, this excludes subdomains. For example, Google fonts, Google Analytics, or any external media/script will be excluded.

Step 2: Blocked resources.

Use your browser to see all the resources that have been blocked (Right click -> Inspect).

Content Security Header inspect in browser.

It’s important to note that different sections of your site can have different blocked resources, be sure to check all of these relevant parts, including the admin area for logged-in users, etc.

For example, take an error from the image above: “Refused to load the font ‘https://cdn.example.com/…’ because it violates the following Content Security Policy directive: “default-src ‘self’”. Note that ‘font-src’ was not explicitly set, so ‘default-src’ is used as fallback.” That means we need to include our CDN domain to the CSP: font-src 'self' https://*.qrokes.com/;

Now you will be able to create something like this:

default-src 'self';

frame-ancestors 'self';

style-src 'self' 'unsafe-inline' https://*.qrokes.com/ https://github.githubassets.com/;

script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.qrokes.com/ https://www.google.com/ https://www.gstatic.com/ https://*.googletagmanager.com/ https://*.google-analytics.com/ https://*.twitter.com/ https://*.ethicalads.io;

frame-src blob: 'self' https://www.google.com/ https://*.twitter.com/;

font-src data: 'self' https://*.qrokes.com/;

img-src data: 'self' https://*.qrokes.com/ https://*.gravatar.com/ https://*.twitter.com/ https://*.w.org https://*.ethicalads.io;
  • All the Google domains are for Analytics and ReCaptcha.
  • The most important here is the ‘unsafe-inline’ and ‘unsafe-eval’, in an ideal world we should never include these options, but unfortunately, they are needed by WordPress core.
  • Most of the content is loaded from ‘self’ and our CDN using a subdomain of qrokes.com.
  • Twitter, GitHub and Gravatar are needed to display some tweets, code gists and profile images, respectively.
  • And EthicalAds for the Ads campaign recently added to our site.

It is worth mentioning that the ‘unsafe-inline’ will be really hard to stop using it, especially because the current solution based on nonces or hashes makes no sense when page caching is used.

Step 3: Connected resources.

Here is the tricky part! Until here, you have unblocked all the needed resources and you don’t see errors in your browser. Right?

But some HTTP requests are still blocked, like AJAX, websockets, etc. In short, some services like Google Analytics and some Ads, may not be working as expected.

default-src 'self';

frame-ancestors 'self';

style-src 'self' 'unsafe-inline' https://*.qrokes.com/ https://github.githubassets.com/;

script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.qrokes.com/ https://www.google.com/ https://www.gstatic.com/ https://*.googletagmanager.com/ https://*.google-analytics.com/ https://*.twitter.com/ https://*.ethicalads.io;

frame-src blob: 'self' https://www.google.com/ https://*.twitter.com/;

object-src 'none';

font-src data: 'self' https://*.qrokes.com/;

img-src data: 'self' https://*.qrokes.com/ https://*.gravatar.com/ https://*.twitter.com/ https://*.w.org https://*.ethicalads.io https://*.google-analytics.com https://*.googletagmanager.com; 

connect-src 'self' https://*.qrokes.com/ https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.ethicalads.io;

We have included the connect-src directive. For Google Analytics they have properly documented the CSP use. As you can see, some image-src was also needed for Google Analytics.

If you are using Webinoly, this would be the complete line:

header-csp:default-src 'self'; frame-ancestors 'self'; style-src 'self' 'unsafe-inline' https://*.qrokes.com/ https://github.githubassets.com/; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.qrokes.com/ https://www.google.com/ https://www.gstatic.com/ https://*.googletagmanager.com/ https://*.google-analytics.com/ https://*.twitter.com/ https://*.ethicalads.io; frame-src blob: 'self' https://www.google.com/ https://*.twitter.com/; object-src 'none'; font-src data: 'self' https://*.qrokes.com/; img-src data: 'self' https://*.qrokes.com/ https://*.gravatar.com/ https://*.twitter.com/ https://*.w.org https://*.ethicalads.io https://*.google-analytics.com https://*.googletagmanager.com; connect-src 'self' https://*.qrokes.com/ https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.ethicalads.io;

Step 4: Test your CSP directive.

  • Test in your browser.
  • Test your external services like Analytics and Ads.
  • Use some tools like the CSP Evaluator for some recomendations. We included the object-src 'none' directive, and the require-trusted-types-for directive was ommited because it breaks WordPress.

And that’s it! Easy, right?

Lessons learned

  • I found some very reputable WP plugins phoning home even when they have the option to disable “Track Data”.
  • Before I implemented the connect-src I noticed an almost complete drop in the traffic reported (Google Analytics), obviously (that’s how we found this issue). The interesting part is that it was never zero, so it means that CSP is bypassed in some circumstances.
  • Be sure to check all the different parts and sections of your site looking for errors. We are still tweaking the CSP from time to time in very hidden places. Today, for instance, after this post was published we noticed the SEO plugin was blocking some content from our CDN.

Leave a Reply

Your email address will not be published. Required fields are marked *