Content Security Policy

Published: 18 March 2020

Introduction

In our post on Finding and Fixing Cross-site Scripting, we recommended the use of Content Security Policy (CSP) to mitigate the effects of this vulnerability. It does this by allowing you to set up an allow list of resource locations (such as scripts) for your web pages, and therefore inform the browser to block any scripts that do not come from an authorised source. The problem is, you have to set up an allow list of resource locations, or the resource will be blocked.

So initially setting up the header can be difficult, but the benefit of preventing attacks such as Cross-site Scripting, and ClickJacking can be worth it.

Building a Policy

A CSP policy can be supplied to the CSP header which lists approved sources for content, a very simple example is:

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

The CSP header is made up of two parts, the list of directives and the list of approved locations. Directives include:

default-srcA default allow list for where more specific directives have not been supplied.
script-srcSpecifies where JavaScript can be loaded from.
img-srcSpecifies where images can be loaded from.
style-srcSpecifies where CSS can be loaded from.
connect-srcSpecifies where features such as XMLHttpRequest may connect to.
font-srcSpecifies where fonts may be loaded from.
object-srcSpecifies where object, embed, and applet elements may be loaded from.
media-srcSpecifies where audio, video and track elements may be loaded from.
frame-srcFrame-src is for where frames can be loaded from, which was deprecated in CSP Level 2 and then undeprecated in CSP Level 3.
frame-ancestorsSpecifies where this page may be framed from. Setting frame-ancestors to ‘none’ is the equivalent of setting the X-Frame-Options header to DENY.
«p class="mt-5"»These are generally what you expect them to be; for example default-src is the policy used for any resources not otherwise specified. script-src is where JavaScript may be loaded from. font-src is where fonts may be loaded from.

There is also a frame-ancestors directive, which

For example directive you can specify valid sources; such as:

*A wildcard to allow any URL except data: and filesystem: schemes.
‘none’Disables this resource type
‘self’Allows resources from the same-origin
data:Allows the loading of resources via the data scheme, such as base64 encoded images
domain.example.comAllows loading resources from the specified domain
*.example.comAllows loading resources from any subdomain of the specified domain
https://example.comAllow loading resources from the specified domain, if supplied over HTTPS
https:Allow loading resources from any domain, if supplied over HTTPS
‘unsafe-inline’Allows the use of inline JavaScript; considered unsafe as it may allow for Cross-site Scripting attacks.
‘unsafe-eval’Allows the use of JavaScript eval(), a function, which may allow for DOM-XSS attacks. The eval() function is described by MDN as “an enormous security risk”.
‘sha256-‘Allow scripts to execute if they match the supplied hash. Sha256, sha384, or sha512 may be used.
‘nonce-‘Allow scripts to be used if they include a nonce= attribute which matches the supplied none entry in the header. These numbers should not be used for more than one script, instead additional nonce- entries should be supplied.

The source list can be used, with the directives to build a policy. For example:

Content-Security-Policy: default-src: 'none'; script-src: js.example.com; img-src: static.example.com; font-src: static.example.com;

Seems pretty easy, right?

On the surface it may sound like a simple thing to come up with a list of all resource locations, as a counterpoint – here’s Twitter’s CSP header, at the time of writing:

content-security-policy: connect-src ‘self’ blob: https://*.giphy.com https://*.pscp.tv https://*.video.pscp.tv https://*.twimg.com https://api.twitter.com https://caps.twitter.com https://media.riffsy.com https://pay.twitter.com https://sentry.io https://ton.twitter.com https://twitter.com https://upload.twitter.com https://www.google-analytics.com https://app.link https://api2.branch.io https://bnc.lt https://vmap.snappytv.com https://vmapstage.snappytv.com https://vmaprel.snappytv.com https://vmap.grabyo.com https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://smdhdsnappytv-vh.akamaihd.net https://smpdhdsnappytv-vh.akamaihd.net https://smmdhdsnappytv-vh.akamaihd.net https://rmdhdsnappytv-vh.akamaihd.net https://rmpdhdsnappytv-vh.akamaihd.net https://rmmdhdsnappytv-vh.akamaihd.net https://dwo3ckksxlb0v.cloudfront.net ; default-src ‘self’; form-action ‘self’ https://twitter.com https://*.twitter.com; font-src ‘self’ https://*.twimg.com; frame-src ‘self’ https://twitter.com https://mobile.twitter.com https://pay.twitter.com https://cards-frame.twitter.com ; img-src ‘self’ blob: data: https://*.cdn.twitter.com https://ton.twitter.com https://*.twimg.com https://www.google-analytics.com https://www.periscope.tv https://www.pscp.tv https://media.riffsy.com https://*.giphy.com https://*.pscp.tv; manifest-src ‘self’; media-src ‘self’ blob: https://twitter.com https://*.twimg.com https://*.vine.co https://*.pscp.tv https://*.video.pscp.tv https://*.giphy.com https://media.riffsy.com https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://smdhdsnappytv-vh.akamaihd.net https://smpdhdsnappytv-vh.akamaihd.net https://smmdhdsnappytv-vh.akamaihd.net https://rmdhdsnappytv-vh.akamaihd.net https://rmpdhdsnappytv-vh.akamaihd.net https://rmmdhdsnappytv-vh.akamaihd.net https://dwo3ckksxlb0v.cloudfront.net; object-src ‘none’; script-src ‘self’ ‘unsafe-inline’ https://*.twimg.com https://www.google-analytics.com https://twitter.com https://app.link ‘nonce-MTBhYjc1MjMtNWMxNS00MjJhLWJmNGEtOTM1MjBmZmM3YTA2’; style-src ‘self’ ‘unsafe-inline’ https://*.twimg.com; worker-src ‘self’ blob:; report-uri https://twitter.com/i/csp_report?a=O5RXE%3D%3D%3D&ro=false

You’ll often see posts highlighting the risks of unsafe-inline and unsafe-eval; however there are several other risks – for example “https:” as a source for default-src or script-src would allow for JavaScript execution (as it allows JavaScript to be loaded from ANY https site). Additionally, if restrictions are not placed on object-src it could allow for objects such as Flash elements to be included which can execute JavaScript.

Report-Only

As CSP will block requests to resources by default, this can break a website if those resources are required for functionality. Therefore it may be beneficial to set up the initial CSP configuration in “Report-Only mode”. This can be achieved using the following header, with the same syntax as a standard CSP header:

Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi

Here the “default-src ‘self’;” is just a placeholder for the CSP configuration we will develop in the next section – the key part is the “report-uri” which will send a JSON message to the supplied URL informing you of what would have been blocked, if you were running in blocking mode. The message will look something like this:

{
"csp-report": {
"document-uri": "https://example.com/page.php",
"referrer": "",
"blocked-uri": "https://example.com/main.css",
"violated-directive": "style-src cdn.example.com",
"original-policy": "default-src 'none'; style-src cdn.example.com;
report-uri https://example.com/report-uri.php"
}
}

Therefore you can set up a page on your site to handle those reports however you would like – alternatively there are services that can catch these reports for you, such as Report-URI.com.

Read More