Content Security Policy (CSP) is a web security standard designed to prevent cross-site scripting, data injection attacks and other security hacks. Adding a CSP to your website is an easy, powerful way to improve security.
A typical web page loads a variety of external resources: JavaScript files, stylesheets, images, font files, files from CDNs, etc. A CSP prevents attacks by specifying the resources a page is allowed to load and the sources from which they can be loaded. It blocks all scripts, images, files from sources not listed in the CSP.
A web page can use <form> actions, it can POST and it can fetch and make API calls. A CSP can specify the URLs which can be called. Calls to other URLs will be blocked.
A web page can be imbedded in another page using <frame>, <iframe>, <object> or <embed> tags. A CSP can specify valid parents that can embed the page. Attempts to embed and display the page in a different parent will be blocked. Likewise, a web page can embed child elements. A CSP can specify valid URLs and sources for embedded content. Attempts to embed content from other sources will be blocked.
A web page can contain scripts inside <script> tags. Without a CSP, these scripts will execute. If the page has a CSP, no inline script will execute unless allowed by use of a nonce or hash (explained below).
A CSP acts as a gatekeeper. It lets you specify which sources of content are trusted, and it blocks everything else. This makes it much harder to inject malicious content into your web pages, enhancing security.
CSP Syntax
Your CSP is defined in a Content-Security-Policy header in your web page. A CSP contains one or more directives. For example,
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://trusted.cdn.com;
img-src 'self' https://images.com/;">
</head>
This example contains three CSP directives.
- default-src ‘self’: This sets the default source for all content. In this case, the default source is ‘self,’ which means content must come from the same origin as the web page itself. That is, if the web page is located at mysite.com, all the external content must also come from mysite.com. Script files, images, etc., which come from a different source are blocked. JavaScript contained within <script> tags and CSS contained within <style> tags on your page will also be blocked. Only scripts and stylesheets loaded from the sources specified in the CSP will execute.
- script—src ‘self’ https://example.com/myscript.js: This allows scripts to be loaded from the same origin (‘self’) or from the specified url. In this case, the URL only allows one file, myscript.js, to be loaded. Scripts from other sources will be blocked.
- img-src ‘self’ https://images.com/: This specifies that images must come from the same origin as the web page or from https://images.com/.
Other CSP Directives
There are many other directives including.
- connect-src <sources>: This restricts URLs which can be loaded by fetch(), XMLHttpRequest, WebSocket, or EventSource. If no connect-src directive is provided, the default-src will be used.
- frame-ancestors <sources>: This restricts which parent sources can embed the page.
- form-action <sources>: This restricts valid URLs for <form> actions.
- media-src <sources>: Restricts valid sources for audio and video files.
- child-src <sources>: Restricts sources that can be used in embedded iframes and web workers.
Permitted Sources
As you can see, these directives all follow the format: xxx <sources>.
The source list can include URLs. Those URLs can contain wildcards. For example,
- *://example.com allows resources to be loaded from any scheme – http, https, etc.
- https://*.example.com has a wildcard to allow all subdomains of example.com.
- default-src * has a wildcard to allow any URL.
In addition to URLs, there are other important sources, including ‘self’ and ‘none’. These must be enclosed in single quotes.
Nonces
If you add a CSP to your web page, it will prevent the execution of inline scripts. Things like this will no longer execute.
<script>doSomethingBad();</script>
Inline scripts will only execute if you use a nonce or hash. Here is an example of a nonce.
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' ‘nonce-654def’ ‘nonce-abc432’>
</head>
<script nonce=”654def”>
console.log(‘This script has a valid nonce and will execute.’);
</script>
<script nonce=”abc987”>
console.log(‘Invalid nonce. This script will be blocked.’);
</script>
<script>
console.log(‘No nonce. This script will be blocked.’);
</script>
In this example, scripts with nonce-654def or nonce-abc432 will execute. Scripts with other nonce values, or no nonce value, will be blocked.
Nonces can also be used for stylesheets.
<link rel="stylesheet" href="/styles.css" nonce="654def">
A nonce can also be used to load a file from a source not listed in the CSP. For example,
<script src=”https://mycdn.com/myfile.js” nonce="ZTIwNjODgY5Jk"></script>
However, not every browser will allow this, so it’s best to list the file in a CSP directive.
In theory, a nonce could be hard coded into the header of a webpage and used in the body. But if the nonce is hard coded and never changes, hackers could add the same nonce to their malicious content. When implementing a CSP, a new nonce needs to be generated with every response from the server, and all the inline scripts need to use the new value. Each time the page is reloaded, it should have new nonce values. Your server-side code that generates and returns content must generate and insert a new nonce value.
Hashes
Hashes work a lot like nonces, but with important differences. Here is a script.
<script>
doSomething();
</script>
The SHA 256 hash for doSomething(); is
44558f2c36efd8163eac2903cec13ed1fafcca51abd91d9f696321ab895f1107
This hash value can be used in a CSP directive, for example
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'none';
script-src 'sha256-44558f2c36efd8163eac2903cec13ed1fafcca51abd91d9f696321ab895f1107';">
</head>
Now the script <script>doSomething();</script> will work, because hashing the script produces the value permitted by the CSP.
Hashes have an important performance advantage over nonces. As mentioned above, a new nonce needs to be generated every time the server returns a page. Not only does this require processing power, it means the page cannot be cached on the server, because the nonce values must change with every response. A hash, however, is only generated once. The only time a new hash value is needed is when the script itself changes. Because the hash does not need to be recalculated with each server response, the page can be cached on the server.
Implementation of CSP
Implementation of a basic CSP without nonces or hashes can be done by hard coding the CSP into the header of the page. A common technique, however, is to add the server configuration, or to the Apache or Nginx proxy settings, so it is injected into each page automatically.
Generating a new nonce value for each server response requires some server-side code.
For example
const crypto = require('crypto');
const nonce = crypto.randomBytes(16).toString('base64');
<script nonce="${nonce}">
// Script goes here
</script>
So now you have the basics of Content Security Policy. The syntax includes additional features not covered here. But this is enough for you to create and implement a CSP for most use cases. Adding a CSP provides important protection against attack. It's worth adding to your page.