Not long ago, I was tasked to assess our current Content Security Policy (CSP) and find out ways to improve it. It took me longer than I thought, as I need to familiarise myself with the terminologies and protocols before go into implementation detail.
What is CSP?
Content-Security-Policy(CSP) is the name of a HTTP response header (or as meta
tag) that browsers use to safeguard your site.
You may ask “aren’t we already have CORS and Same-site policy? Well, attackers have found clever ways to compromise the system. For example, Cross-site scripting (XSS) attack originates from the browser’s inability to distinguish between script from your application and script that’s been maliciously injected by a third-party but faked to be part of your application. With CSP, the server defines a whitelist of content from trusted sources.
How to add CSP?
1. as headerContent-Security-Policy: <directive> <value>; <directive> <value>; <directive> <value>;2. as meta tag<meta http-equiv="Content-Security-Policy" content="<directive> <value>; <directive> <value>; <directive> <value>;">
The CSP mechanism allows multiple policies to be specified, via the Content-Security-Policy
header( Content-Security-Policy-Report-Only
header) and a <meta>
element.
You can even implement multiple policies. But with multiple policies, the CSP take the most strict ones.
Apart from that, the CSP should include a default-src
policy directive, which is a fallback for most other resource types.
Possible directives can be:
base-uri
URLs that can appear in<base>
element.child-src
URLs allowed for workers and embedded frame contents, e.g.child-src https://video.com
.frame-src
if not present it still falls back tochild-src
connect-src
origins that you can connect to, e.g. via XHR, WebSockets, and EventSource.font-src
origins that can serve web fonts.form-action
valid endpoints for submission from<form>
tags.frame-ancestors
sources that can embed the current page, which applies to<frame>
,<iframe>
,<embed>
, and<applet>
tags.img-src
origins from which images can be loaded.media-src
origins allowed to deliver video and audio.object-src
control over Flash and other plugins.plugin-types
plugins that a page may invoke.report-uri
a URL where a browser will send reports to, unavailable through<meta>
tags.style-src
origins allowed for stylesheets.
Note that the following directives don’t use default-src
as a fallback: base-uri
, form-action
, frame-ancestors
, plugin-types
, report-uri
, sandbox
.
Below are possible values matching with directives:
<host-source>
: Internet hosts by name or IP address, as well as an optional URL scheme and/or port number:http://*.example.com
,example.com:443
,*.example.com.
<scheme-source>
: A scheme such ashttp:
orhttps:
,data:
.- '
self'
: Refers to the origin from which the protected document is being served, including the same URL scheme and port number. 'unsafe-eval'
: Allows text-to-JavaScript mechanisms likeeval
.'unsafe-hashes'
: Allows enabling specific inline event handlers.'unsafe-inline'
: Allows inline JavaScript and CSS such as inline<script>
elements,javascript:
URLs, inline event handlers, and inline<style>
elements.'none':
Nothing allowed.'nonce-<base64-value>'
: A white-list for specific inline scripts using a cryptographic nonce (number used once). The server generates a nonce value each time it transmits a policy.‘<hash-algorithm>-<base64-value>’
: A sha256, sha384 or sha512 hash of scripts or styles.‘strict-dynamic’
: Thestrict-dynamic
source expression is raised in CSP Level 3, and specifies a given <script> element’s contents are authorised to execute if the element includes an attribute nonce whose value matches a nonce given in the HTTP response that served the page. If the nonces do not match (or are not both present) the script will not execute.‘report-sample’
: Requires a sample of the violating code to be included in the violation report.
Inline-script
Adding nonce or hash
Values like unsafe-inline
are dangerous, but sometimes are necessary evil. Especially if you require some 3rd party scripts like Google Tag Manager to run on your website. For that, we can use a cryptographic nonce or a hash as mentioned above.
To use a nonce, give your script tag a nonce attribute. Its value must match one in the list of trusted sources. For example:
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">function(){}</script>
Now, add the nonce to your script-src
directive appended to the nonce-
keyword.
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Another way of doing so is with Hash. To use hash,encrypting all characters inside the <script>
tag and insert the hash value in the script-src
directive, prefixing it with sha256-
, sha384-
, or sha512-.
<script>function(){};</script>==========> Content-Security-Policy: script-src 'sha256-5ef5d1ac865d5989c69073dafa4c847fa199f1777c72dfb16a5d61104b6bc6f3'
Adopting a strict CSP
To enable a strict CSP policy, most applications will need to make the following changes (we use nonce
in our case):
- Adopt a middleware to generate
nonce
. - Convert attribute javascript event to inline javascript event.
- Add a
nonce
attribute to all<script>
elements. Some template systems can do this automatically for example Google Closure. But this is the most difficult part as some framework does not allow you to do that easily. - Test the change in Report header.
Use ‘strict-dynamic'
Since CSP Level 2, nonces have offered an alternative to whitelisting individual origins. But the biggest problem with the nonce is that dynamically generated scripts added at runtime would fail to execute.
All such scripts would have to be refactored and moved off into defined external scripts so that the nonce could be included during page creation.
CSP Level 3 solves this with the strict-dynamic keyword. This keyword makes it so that dynamically generated scripts inherit the nonce from the trusted script that created it.
The good thing is the backward compatibility. When strict-dynamic is used, browsers that support it will ignore the following source list expressions:
- ‘unsafe-inline’
- ‘unsafe-eval’
- ‘self’
- http: or https: host based source lists
So for example ‘unsafe-inline’ https: ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’ ‘strict-dynamic’
will be ‘unsafe-inline’ https:
in CSP1, https: ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’
in CSP2, and ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’ ‘strict-dynamic’
in CSP3.
CSP Examples
- Allow everything but only from the same origin:
default-src 'self';
- Allow inline script Google Analytics, and same origin:
script-src 'self' unsafe-inline www.google-analytics.com;
- Allow content from a trusted domain and all its subdomains
default-src 'self' trusted.com *.trusted.com
- Allow images from any origin in their own content, and from cdn i
mg-src ‘self’ *.cloudfront.net
;
Evaluate your CSP
There are some useful chrome extensions to help you build or monitor your CSP policy, or you can just copy your CSP and paste it in the validator like https://csper.io/.
That’s today’s content!
Happy Reading!