Content Security Policy
Published: 18 March 2020
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-src||A default allow list for where more specific directives have not been supplied.|
|img-src||Specifies where images can be loaded from.|
|style-src||Specifies where CSS can be loaded from.|
|connect-src||Specifies where features such as XMLHttpRequest may connect to.|
|font-src||Specifies where fonts may be loaded from.|
|object-src||Specifies where object, embed, and applet elements may be loaded from.|
|media-src||Specifies where audio, video and track elements may be loaded from.|
|frame-src||Frame-src is for where frames can be loaded from, which was deprecated in CSP Level 2 and then undeprecated in CSP Level 3.|
|frame-ancestors||Specifies where this page may be framed from. Setting frame-ancestors to ‘none’ is the equivalent of setting the X-Frame-Options header to DENY.|
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.com||Allows loading resources from the specified domain|
|*.example.com||Allows loading resources from any subdomain of the specified domain|
|https://example.com||Allow loading resources from the specified domain, if supplied over HTTPS|
|https:||Allow loading resources from any domain, if supplied over HTTPS|
|‘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
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:
"violated-directive": "style-src cdn.example.com",
"original-policy": "default-src 'none'; style-src cdn.example.com;
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.
Posts broken down by category
Articles concentrating on network and operating system level attacks.
Articles covering attacks against web applications and their associated APIS.
Articles concentrating on past data breaches, looking for lessons learned.
Articles covering breaking into wireless networks and how to keep them safe.