How to Get Started with Your Website Content Security Policy
The web is based on a “same-origin” policy. Only code at mysite.com can access mysite.com’s data in cookies, localStorage, Ajax requests etc. It is isolated from other domains so any access attempts from evilsite.com will be rejected.
Unfortunately, it’s never that simple. Modern websites are complex and load a variety of third-party components, styles and scripts. A script loaded from another domain runs in the context of the current page and can do whatever it likes. That social networking button could monitor visitors, hijack login cookies, change page content and more. Even if you trust the third-party site, you could become victim to a man-in-the-Middle attack where the script is changed before it reaches you. Alternatively, it could permit users to launch their own Cross Site Scripting attacks (XXS).
By default, browsers implement an anything-goes approach. Fortunately, it’s possible to apply restrictions using a Content Security Policy (CSP) which prevent unexpected security issues. A CSP tells the browser what’s permitted, e.g. run JavaScript at mysite.com but only from files and not inline <script>
tags.
Test Your Website
To check whether CSP is implemented on your site, visit observatory.mozilla.org, enter a page URL and hit Scan Me. Those with no CSP protection are likely to score an F (although various other checks are made).
CSP should be considered essential for banks, online stores, social networks and any site which implements user accounts. It’s less necessary if your site doesn’t use third-party scripts, fonts, media, widgets or analytics but can you be sure it never will?
Implementing a Content Security Policy
A Content Security Policy must be added to each page by your developer or web host. It’s defined using a Content-Security-Policy HTTP header set by a server-side language (PHP, Node.js, Ruby etc.) or within the server configuration such as Apache’s .htaccess
file, e.g.
# Apply a CSP to all HTML and PHP files
<FilesMatch "\.(html|php)$">
Header set Content-Security-Policy "policy-definition"
</FilesMatch>
(We’ll discuss the “policy-definition” value shortly.)
Server configuration files are practical because they apply the same header to all pages within the sub-folder hierarchy. However, you can also define a policy within the HTML <head>
of any page using a meta
tag:
<meta http-equiv="Content-Security-Policy" content="policy-definition">
This may be necessary if you don’t have permission to configure the server or require differing policies on each page.
Content Security Policy Definition
Now for the complex part. CSPs define a whitelist of permitted domains and contexts for differing types of content.
Presume you only want to permit scripts loaded from your domain. You could use the following CSP (please don’t do this for real yet — it’s just an example!):
script-src 'self';
You then realise you’re also loading a third-party library from a CDN which can appear on various sub-domains of mycdn.com
. A domain wildcard is added to the space-separated list:
script-src 'self' *.mycdn.com;
You then remember some of your scripts run inline on the page — we can define that too:
script-src 'self' *.mycdn.com 'unsafe-inline';
We now have a policy for scripts. However, we’ve not defined other types so all stylesheets, images, fonts, etc. would fail to load. To solve this, we can apply a default policy using default-src
which serves as a fallback for any undefined type:
default-src 'self'; script-src 'self' *.mycdn.com 'unsafe-inline';
Note that each content type definition is separated with a semi-colon (;). We can now use this policy in our .htaccess
file:
Header set Content-Security-Policy "default-src 'self'; script-src 'self' *.mycdn.com 'unsafe-inline';"
or a page meta tag:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' *.mycdn.com 'unsafe-inline';">
CSP Directive Reference
The full set of CSP directives:
directive | description |
---|---|
default-src |
the default fallback policy. Typically set to 'self' or 'none' to ensure all other directives must be declared |
style-src |
valid stylesheet sources |
script-src |
valid JavaScript sources |
connect-src |
valid Ajax, WebSocket or EventSource sources for JavaScript data retrieval |
form-action |
valid sources for form action attributes |
img-src |
valid image sources |
font-src |
valid font sources |
media-src |
valid HTML5 audio and video element sources |
object-src |
valid plugin sources for HTML object , embed and applet elements |
plugin-types |
valid MIME types for plugins invoked by object and embed , e.g. application/pdf |
frame-src |
valid frame and iframe sources (now deprecated — use child-src instead) |
child-src |
valid frame and iframe sources |
frame-ancestors |
valid embedding sources for frame , iframe , object , embed and applet elements |
sandbox |
enables a sandbox for the resource in a similar way to the HTML5 iframe sandbox attribute. This has a number of restrictions unique to this directive: allow-forms , allow-same-origin , allow-scripts , allow-popups , allow-modals , allow-orientation-lock , allow-pointer-lock , allow-presentation , allow-popups-to-escape-sandbox , and allow-top-navigation |
report-uri |
an address where the browser can POST reports of policy failures |
CSP Sources Reference
The CSP source directives ending -src
support the following values. Any number of space-separated values can be used:
source | description |
---|---|
'none' |
prevents loading from any source, e.g. frame-ancestors 'none' stops the page showing any iframe or plugin. The value cannot be followed by other sources |
'self' |
allows loading from sources on the same origin (protocol, domain/IP and port) |
https: |
only allows sources on HTTPS connections |
data: |
permits data: sources, e.g. style-src data: allows base64-encoded images in your stylesheets |
* |
wildcard for any URL |
*.domain.com |
permits sources from any sub-domain of domain.com, i.e. www.domain.com, cdn.domain.com, etc. |
exact.domain.com |
permits sources from exact.domain.com |
https://exact.domain.com/ |
permits HTTPS sources on the given domain |
'unsafe-inline' |
permits inline CSS, scripts, javascript: URIs, and element event handlers such as onclick within the HTML |
'unsafe-eval' |
permits unsafe dynamic code using JavaScript’s eval() function |
'nonce-id' |
permits an inline CSS or script to run if the id matches the nonce attribute value, e.g. script-src 'nonce-abc123' runs inline code within a <script nonce="abc123">...</script> block |
'sha256-hash' |
permits styles or scripts if the file content matches the generated SHA-256 hash value |
CSP Development Recommendations
It’s practical to start with a strict default policy of default-src 'none';
then add further permissions as required. A good starting point for the majority of websites could be:
default-src 'none'; style-src 'self' data:; img-src 'self' data:; script-src 'self'; connect-src 'self';
This permits styles, images, scripts and Ajax requests from the same origin.
Open your page in a web browser then launch the developer tools console. Blocked resource warnings will be reported, e.g.
Refused to load the script 'XXX' because it violates the following Content Security Policy directive: "YYY".
You may need to browse various pages to ensure you’ve accounted for all the fonts, images, videos, scripts, plugins and iframes your site requires.
Google Services
Google provides a great range of services and you’re possibly using analytics, fonts, maps and more. Unfortunately, these are enabled on a range of URIs which require further Ajax calls, inline execution and data schemes. You may end up with a convoluted policy such as:
default-src 'self';
style-src 'self' 'unsafe-inline' *.googleapis.com;
script-src 'self' *.google-analytics.com *.googleapis.com data:;
connect-src 'self' *.google-analytics.com *.googleapis.com *.gstatic.com data:;
font-src 'self' *.gstatic.com data:;
img-src * data:;
(Line breaks have been added for clarity but must not be used in real code.)
This cannot be avoided at the time of writing and other third-party vendors will have similar challenges.
Test Again
Finally, re-test your pages again at observatory.mozilla.org and, with luck, your Content Security Policy grade has improved significantly. The tool will also advise about older browsers, HTTPS, CORS, MIME, cookies, referrer and redirection policy headers.
Implementing a Content Security Policy is an important step in the prevention of unexpected security issues. Another important step is the selection of a hosting provider that takes security to heart. Our partner, SiteGround, is a great option for anyone looking for a web hosting platform built for advanced website security.