Post

Intro to Content Security Policy

Get started with Content Security Policy (CSP) with this brief tutorial

Intro to Content Security Policy

1. Introduction

Content Security Policy (CSP) is an extra layer of security that helps to protect websites from cross-site scripting (XSS). The core idea of CSP is to describe rules that define which resources are allowed on a web page.

Using CSP in combination with traditional methods such as input sanitization and output encoding is a great way to enhance the security of a website.

The W3C Web Application Security Working Group governs the standard. Level 2 is currently the most widespread version, with the vast majority of browsers supporting it.

2. Enabling Content Security Policy

We can introduce CSP on a web page in two ways:

  • specifying the Content-Security-Policy response header
  • specifying a meta tag in the HTML document

2.1. Specifying the Response Header

To enable CSP from the server side, we need to add the Content-Security-Policy response header. The browser sees the header and loads resources only from the permitted sources. For example, a rule can say that we allow loading scripts from the current domain:

1
Content-Security-Policy: script-src 'self'

Adding a script from a different domain does not succeed. For example, the browser would refuse to execute the following Google Analytics script:

1
<script src='https://www.google-analytics.com/analytics.js'></script>

Google Analytics is not loaded

To allow loading images only from Wikimedia Commons, we could introduce an extra rule separated by a semicolon:

1
Content-Security-Policy: script-src 'self'; img-src upload.wikimedia.org

If we add two images to the web page, one from the permitted domain and one from a different one, only the first image is visible:

1
2
3
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3f/JPEG_example_flower.jpg"/>

<img src="https://images.unsplash.com/photo-1539657523674-fbd149b04c13?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80"/>

2.2. Specifying a meta Tag

We can also introduce CSP via an HTML meta tag:

1
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">

When using the meta tag, it has to be placed as early in the document as possible. All the resources before the tag are loaded and parsed without any restrictions. Hence, there’s a possibility that the CSP meta tag might be tampered with by a resource that appears in the HTML document earlier than the tag itself.

Note that some directives, such as report-uri and frame-ancestors, are not supported in the meta tag.

3. Executing Inline Scripts

It’s quite common to add inline scripts, such as Google Analytics, to a website. By default, executing inline scripts is forbidden once a script-src directive is set. To enable it, we need to specify the 'unsafe-inline' source:

1
script-src 'unsafe-inline'

However, we have a problem now. We have allowed executing any inline script, while we only want to run the Google Analytics script. How do we fix the issue?

3.1. Using a Nonce-based Policy

The first option would be to use a nonce-based policy.

We need to specify the following policy in the header:

1
script-src 'nonce-hQPT4OcC0R/48p8l3QLLkw=='

To allow executing specific scripts, we need to add a nonce attribute to the script we want to run. If the attribute value matches the nonce value in the header, the script is executed.

1
2
3
<script nonce="hQPT4OcC0R/48p8l3QLLkw==">
 alert('Hello, World!');
</script>

If the nonce in the header doesn’t match the nonce attribute, the script won’t be executed. This prevents malicious parties from executing their scripts on the website.

The critical point in this approach is to generate a random, unguessable nonce every time we serve the page to a client. If we keep the nonce the same, we expose our website to XSS once again.

3.2. Using a Hash-based Policy

A hash-based policy is an alternative to the nonce-based one.

The approach lets us allow specific inline scripts that we would like to execute. Suppose we want to add the following inline script to our website:

1
<script>alert('Hello, world.');</script>

We generate a SHA-256, SHA-384, or SHA-512 hash of the script contents and base64-encode it:

1
echo -n "alert('Hello, world.');" | openssl dgst -sha256 -binary | openssl enc -base64

The output is a unique representation of the allowed inline script. To enable it, we need to specify the hash as a source:

1
script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='

This directive allows us to specify the exact inline scripts we allow running.

4. Reporting

Even though CSP can save us from security holes, it poses a problem. What if we accidentally block a valid resource? Our website might not look like it’s supposed to or miss vital functionality. To avoid the problem, we can take advantage of CSP’s reporting functionality.

CSP reporting enables us to receive reports on any violations. To enable reporting, we need to specify the report-uri directive:

1
report-uri /report-violation

If a violation occurs, a POST request is made to the specified URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "csp-report": {
    "document-uri": "http://localhost:3000/",
    "referrer": "",
    "violated-directive": "font-src",
    "effective-directive": "font-src",
    "original-policy": "default-src 'self'; script-src 'sha256-2iWaz2HO1LXYyykhs81x9Qb/Mcp0RDG25FqpEk86u38='; report-uri /report-violation",
    "disposition": "enforce",
    "blocked-uri": "data",
    "status-code": 200,
    "script-sample": ""
  }
}

While rolling out a new policy, we can also send it in the Content-Security-Policy-Report-Only header instead. The browser then reports violations without blocking anything, so we can fix the policy before enforcing it. Note that report-uri is deprecated in favour of report-to, but it is still the most widely supported option.

5. Conclusion

All in all, CSP helps us to protect websites from XSS by blocking unwanted resources, such as stylesheets, script files or images. We can enable CSP by either specifying appropriate directives using the HTML meta tag or headers in the HTTP response. The latter is the preferred approach.

© Kert Pjatkin. All rights reserved.