What is a Content Security Policy (CSP)?

A Content Security Policy is an extra security layer, that is easy to implement and monitor. It can be used to detect and mitigate attacks like Cross Site Scripting (XSS) and data injection attacks on your site that could lead to data- or cookie theft, changing your site's appearance (defacement) or distribution of malicious scripts and software.

Why use it and how does it work?

The main reason for publishing a CSP is to better protect your site's visitors from malicious code being executed on your website. Setting up a CSP allows you to selectively specify what content is allowed to be loaded by whitelisting specific origins, sources, nonces and hashes. This way you can avoid external scripts from being downloaded and executed. This greatly reduces the attack vector where malicious scripts can harm your visitors and brand name.

Here's a short example that explains how CSP works:

  1. A user visits your website
  2. The browser fetches the website’s headers (where the CSP is specified)
  3. The browser parses the CSP and remembers which sources are allowed to be loaded.
  4. The browser now starts to load additional resources that are defined in your source code, like Google Analytics, fonts, images, css, inline scripts, etc.
  5. Each resource is cross-referenced to the page’s CSP, and if a source is not specified the resource is blocked.
  6. If specified, a violation report will be sent from the browser to a report endpoint like URIports.

Adoption

Based on the (February 2020) data from Scott Helme's Crawler.Ninja, just over 5% of the websites in the Alexa top 1 million publish a Content Security Policy, so there is room for improvement. It will make the internet a safer place for all of us. So let's get to work and read about implementation steps. 😊

A complete walkthrough on how to implement a solid CSP

In this walkthrough we will help you build your CSP from the ground up and walk you through the steps you need to take to smoothly build and test your CSP without breaking your site in the process. We recommend using Google Chrome in these steps because of their extensive console and detailed messages on CSP violations. I also recommend disabling plugins while testing, because plugins can trigger violations that you cannot and should not fix.

Step 1: Start with a basic CSP header

There are two CSP headers: one that enforces violations, and the other only report violations. Both headers can be used simultaneously, but let's start with the report-only header so you don't break your site and you can see what violations are triggered when you visit your own site with a basic policy.

Alright, lets get started. Add the following response header to your site:

Content-Security-Policy-Report-Only: default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'

Do not know how to add headers? Check our short guide.

You now have a report-only policy that tells the browser:

  • default-src: if resources are not defined in the other sections, use this policy
  • font-src: defines what fonts may be loaded
  • img-src: defines what images may be loaded
  • script-src: defines what scripts may be loaded
  • style-src: defines what styles/css may be loaded

The first argument, (e.g. default-src) is called a directive. All the directives in the example have 'self' as a trusted source. This tells the browser to only load resources that are hosted on the same (sub)domain.

Step 2: Start monitoring in the browser and check violations

Start Google Chrome, go to your website and press F12 to open the developer tools and console. In the console you can monitor the CSP violations. When you have opened the console, refresh the page and check the violations that appear in your console.

Step 3: Check and fix the violations

You will probably trigger many violations in the console. Most violations you see happen because your policy isn't setup correctly yet, that's the next step. There are two ways of fixing violations: remove the code that triggers the violations or whitelist the source in your CSP. Let's start with whitelisting.

Whitelist external sources

There are multiple ways to whitelist a source, here are a few examples.

Example 1

In this example we whitelist our own (sub)domain and we whitelist all the content that comes from a domain we trust (*.example.com), the domain may be used for anything like images, scripts, media, etc. because it's defined in the default-src directive.

Content-Security-Policy-Report-Only: default-src 'self' *.example.com

Example 2

It is possible to be more strict in your policy. In this example we whitelist our own domain as default (self), the domain images.com for images and the (sub)domain myscripts.otherwebsite.com for scripts. Images and scripts loaded from other domains are in violation of our policy and will not be loaded when we enforce our policy.

Content-Security-Policy-Report-Only: default-src 'self'; img-src images.com; script-src: myscripts.otherwebsite.com

Example 3

When defining sources in your CSP, we recommend to be as strict as possible. When you want to whitelist a javascript file that's hosted on for example cdnjs.cloudflare.com, do not whitelist the complete domain, but specify the exact file or directory you would like to whitelist. Whitelisting the complete domain opens up your site for other possible malicious scripts that are hosted on that domain.

Refer to the exact external resource:

Content-Security-Policy-Report-Only: default-src 'self'; script-src: https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js

Or if that isn't practical, at least try to be as strict as possible like referring to a specific location:

Content-Security-Policy-Report-Only: default-src 'self'; script-src: https://cdnjs.cloudflare.com/ajax/libs/vue/

The more specific you define your sources, the better you protect your site visitors.

Whitelist inline sources

Even though a CSP can whitelist the usage of inline scripts and styles, it is not recommended to do so. Mainly because allowing it will greatly increase the risk for a malicious script to inject code. There are few options for whitelisting inline code.

Option 1: The best solution: Don't use inline code at all

The best solution is to stop using inline code and move the code to external files. This makes maintaining a CSP easier and makes the website safer for it's visitors. Absolutely recommending this even while this is possibly the most difficult option to choose if your site uses inline scripts and styles.

Option 2: Use a hash

If, for some reason, inline code is absolutely necessary, you can whitelist an inline code block with a hash or a nonce. A hash is the easiest. You need to calculate the hash for a specific inline code block and whitelist that hash in your CSP.

Hash example

You have an inline script running on the website like this:

<script>alert("Hello World!");</script>

Your CSP will block this script from running and you will see in the developer console that this inline script triggered a violation. To allow this inline script, check the message in the console. It contains the sha256 hash that you can include in your policy. The message in the developer console looks something like this:

[Report Only] Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-vtOwtCfiL2B+TrRWnLTdfTIr7KTaqohZywH93jHLSGw='), or a nonce ('nonce-...') is required to enable inline execution.

The console message confirms that the hash 'sha256-vtOwtCfiL2B+TrRWnLTdfTIr7KTaqohZywH93jHLSGw=' can be used. Copy the hash and update your CSP like this:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'sha256-vtOwtCfiL2B+TrRWnLTdfTIr7KTaqohZywH93jHLSGw=';

Now the inline code block is whitelisted and the violation is fixed. Remember that the hash is generated based on the content of the inline script. If you change the inline script, the hash will be different and the browser will trigger a violation once again.

Option 3: Use a nonce

Sometimes the use of a hash is unwanted or impractical because the inline code block might contain dynamic data, causing the hash to change too. The alternative to this is the use of a nonce. A nonce is a generated random string that allows you to whitelist a complete code block regardless of it's content. An important security aspect when using a nonce is that you need to generate a new nonce each time a page is loaded and make sure the nonce is not predictable in any way. If an attacker can guess the nonce he will still be able to run inline code. Place the generated nonce in your CSP header dynamically and insert that same nonce dynamically in your page source that contains the inline code blocks.

Here is an example of a nonce in use. This is the generated CSP header:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-VVJJcG9ydHMuY29tIGlzIHRoZSBiZXN0';

Now add the same nonce to your script-tag:

<script nonce="VVJJcG9ydHMuY29tIGlzIHRoZSBiZXN0">...</script>

And remember. Don't use a static nonce, otherwise nonces are nonsense. Pun intended.

There is one option you can use to enable inline code blocks, but please only use this as a last resort, because by enabling this, you allow the possibility for code injection and the main reason for using a CSP is to block malicious code injection in the first place. So don't use this option!

To allow unsafe inline scripts and styles, add the value 'unsafe-inline' in your CSP. In this example we have enabled the use of inline scripts and inline styles.

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';

Feeling dirty already for enabling unsafe-inline? You should be.

Alright, all violations fixed? Let's take the next step.

Step 4: Enable real-time reporting

After you have resolved all violations and the developer console isn't displaying any more violations, you can enable the URIports reporting tool to start monitoring any violations that might occur when other visitors use your site.

Get a URIports account

If you do not have a URIports account yet, sign up for a 30-day trial account and log in. Locate your personal report endpoint url (like: example.uriports.com) in the top right part of your dashboard in your account.

Update the headers

You need to update your current CSP-header and add an extra header to enable reporting. Add the directive "report-uri" and specify your report endpoint. Replace "example" with your own URIports account subdomain.

Content-Security-Policy-Report-Only: default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; report-uri https://example.uriports.com/reports/report; report-to default

To enable the CSP ‘report-to’ directive, you need to add an additional header to specify an endpoint for the Reporting API:

Report-To: {"group":"default","max_age":10886400,"endpoints":[{"url":"https://example.uriports.com/reports"}],"include_subdomains":true}

After you have published the headers, you will start receiving reports in your URIports account from visitors that trigger violations.

Step 5: Monitor and check the received reports

In your URIports account, all received CSP reports are bundled, prioritized and enriched to give you as much insight as possible of the violations that were triggered on the website.

The CSP violations are sorted on importance. Violations that are being triggered by multiple sources (unique website visitors) are the ones that probably require your attention. Those violations can be spotted by the red number on the right in the "C / S" column.

Inspect a report

Click the "Inspect" button on the right to inspect a group of CSP violations. The inspector will give you an overview of the different types of browsers that triggered the violation, where the violation happened and which directive was violated. All individual reports are also shown. To whitelist the violation; click the button "View or allow source". This will open the CSP advisor that will suggest a possible solution to avoid the violation in the future. Suggestions are based on the reported CSP by the browser. Be careful and verify the policy before you implement it.

Hiding the violation

When you have resolved a violation, you can hide the reports by clicking the small arrow on the right of the "Inspect" button and choose the option "Hide". This will help you clean up your view so you can focus on the rest. If the violation does happen again, the new reports will appear in the list again.

Step 6: Enforce your CSP policy

When you are confident that your CSP is setup correctly, you can enforce your policy. When your policy is enforced, the browser will not only report violations, but also stop sources from being loaded and executed, thus making the website a safer place. 👍

To enforce your policy, change the header key from Content-Security-Policy-Report-Only to Content-Security-Policy.

So the header:

Content-Security-Policy-Report-Only: ...

Becomes:

Content-Security-Policy: ...

That's it! Well done!

Step 7: Create rules in URIports for unactionable violations

In your URIports account, you have the option to create ignore and block rules for violations that you cannot fix or violations that you want to ignore. Read more about creating rules in our blog.

Final thoughts

We hope this guide was helpful for understanding and implementing a Content Security Policy. By combining it with URIports you can make it even more valuable, allowing you to monitor, analyze and fix violations. A correct and strict CSP will definitely create a safer website and will protect your visitors. And speaking on behalf of all surfers on the web; thank you for helping to create a safer internet for us all.

If you have any more questions about the Content Security Policy, just drop me a line at @roelandkuiper or @uriports