How Does Content Security Policy (Csp) Work

How does Content Security Policy (CSP) work?

The Content-Security-Policy meta-tag allows you to reduce the risk of XSS attacks by allowing you to define where resources can be loaded from, preventing browsers from loading data from any other locations. This makes it harder for an attacker to inject malicious code into your site.

I banged my head against a brick wall trying to figure out why I was getting CSP errors one after another, and there didn't seem to be any concise, clear instructions on just how does it work. So here's my attempt at explaining some points of CSP briefly, mostly concentrating on the things I found hard to solve.

For brevity I won’t write the full tag in each sample. Instead I'll only show the content property, so a sample that says content="default-src 'self'" means this:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

1. How can I allow multiple sources?

You can simply list your sources after a directive as a space-separated list:

content="default-src 'self' https://example.com/js/"

Note that there are no quotes around parameters other than the special ones, like 'self'. Also, there's no colon (:) after the directive. Just the directive, then a space-separated list of parameters.

Everything below the specified parameters is implicitly allowed. That means that in the example above these would be valid sources:

https://example.com/js/file.js
https://example.com/js/subdir/anotherfile.js

These, however, would not be valid:

http://example.com/js/file.js
^^^^ wrong protocol

https://example.com/file.js
^^ above the specified path

2. How can I use different directives? What do they each do?

The most common directives are:

  • default-src the default policy for loading javascript, images, CSS, fonts, AJAX requests, etc
  • script-src defines valid sources for javascript files
  • style-src defines valid sources for css files
  • img-src defines valid sources for images
  • connect-src defines valid targets for to XMLHttpRequest (AJAX), WebSockets or EventSource. If a connection attempt is made to a host that's not allowed here, the browser will emulate a 400 error

There are others, but these are the ones you're most likely to need.

3. How can I use multiple directives?

You define all your directives inside one meta-tag by terminating them with a semicolon (;):

content="default-src 'self' https://example.com/js/; style-src 'self'"

4. How can I handle ports?

Everything but the default ports needs to be allowed explicitly by adding the port number or an asterisk after the allowed domain:

content="default-src 'self' https://ajax.googleapis.com http://example.com:123/free/stuff/"

The above would result in:

https://ajax.googleapis.com:123
^^^^ Not ok, wrong port

https://ajax.googleapis.com - OK

http://example.com/free/stuff/file.js
^^ Not ok, only the port 123 is allowed

http://example.com:123/free/stuff/file.js - OK

As I mentioned, you can also use an asterisk to explicitly allow all ports:

content="default-src example.com:*"

5. How can I handle different protocols?

By default, only standard protocols are allowed. For example to allow WebSockets ws:// you will have to allow it explicitly:

content="default-src 'self'; connect-src ws:; style-src 'self'"
^^^ web Sockets are now allowed on all domains and ports.

6. How can I allow the file protocol file://?

If you'll try to define it as such it won’t work. Instead, you'll allow it with the filesystem parameter:

content="default-src filesystem"

7. How can I use inline scripts and style definitions?

Unless explicitly allowed, you can't use inline style definitions, code inside <script> tags or in tag properties like onclick. You allow them like so:

content="script-src 'unsafe-inline'; style-src 'unsafe-inline'"

You'll also have to explicitly allow inline, base64 encoded images:

content="img-src data:"

8. How can I allow eval()?

I'm sure many people would say that you don't, since 'eval is evil' and the most likely cause for the impending end of the world. Those people would be wrong. Sure, you can definitely punch major holes into your site's security with eval, but it has perfectly valid use cases. You just have to be smart about using it. You allow it like so:

content="script-src 'unsafe-eval'"

9. What exactly does 'self' mean?

You might take 'self' to mean localhost, local filesystem, or anything on the same host. It doesn't mean any of those. It means sources that have the same scheme (protocol), same host, and same port as the file the content policy is defined in. Serving your site over HTTP? No https for you then, unless you define it explicitly.

I've used 'self' in most examples as it usually makes sense to include it, but it's by no means mandatory. Leave it out if you don't need it.

But hang on a minute! Can't I just use content="default-src *" and be done with it?

No. In addition to the obvious security vulnerabilities, this also won’t work as you'd expect. Even though some docs claim it allows anything, that's not true. It doesn't allow inlining or evals, so to really, really make your site extra vulnerable, you would use this:

content="default-src * 'unsafe-inline' 'unsafe-eval'"

... but I trust you won’t.

Further reading:

http://content-security-policy.com

http://en.wikipedia.org/wiki/Content_Security_Policy

How to understand the Content-Security-Policy (CSP) rules by the popular websites?

default-src * data: blob: 'self';

In my recent studies, the asteroid syntax is usually not suggested which will make your website insecure.

That's generally yes, but most potentially dangerous for XSS the script-src and connect-src directives have their own whitelist of sources without *.

For the remaining fallback-directives, the asterisk * is not a dangerous source (maybe except object-src), on the contrary, it is necessary because the list of third-party hosts for hosting images or media content can be endless.

And I cannot figure out what is the final effect of this rule, if 'self' always overrides *, then why leave a redundant *.

Actually it's * overrides 'self'. And yes, the 'self' is redundant when using *. In large companies, IT specialists do not like to touch what works. I think that "self" just remained as an atavism after some researches, because some older Safari had a bug with 'self'.

I thought nonce is designed to prevent the usage of unsafe-inline, but in the above directive, both exist.

This was made for browsers backward compatibility.

In the presence of a CSP nonce the 'unsafe-inline' will be ignored by modern browsers. Older browsers, which don't support nonces, will see 'unsafe-inline' and allow inline scripts to execute.

Where to specify the Content Security Policy (CSP): on a backend or on a frontend?

Delivering CSP via HTTP header is a preferred way.

Meta tag has the same functionality but for technical reasons it does not support some directives: frame-ancestors, report-uri, report-to and sandbox. Also the Content-Security-Policy-Report-Only is not supported in meta tag.

In SPA (Single Page Application), a meta tag is traditionally used for CSP delivery, because a lot of hostings do now allow to manage of HTTP header.

When SSR (Server Side Rendering), an HTTP header is used more often.

You can use any technically convenient CSP delivery method (keeping in mind the limitations of the meta tag), but do not use both at the same time. Both policies will be executed one after the other, so in case of differences, a stricter one will apply actually.

Note that:

  • CSP meta tag should be placed in <head>, otherwise it will not work.
  • Changing the meta tag by javascript will result in both the old and the new policies being in effect.
  • in cases of CSP for non-HTML files, the meta tag can not be used technically

Is Content Security Policy (CSP) Isolation possible?

Yes, your CSP meta tag will apply to any external scripts the web app loads and in common you have to know every external JavaScript all the web apps load.

But if all scripts you load are from assets.adobedtm.com you can just add this source to the script-src directive:

 script-src assets.adobedtm.com

and all external scripts from it will be allowed.

Also CSP provides a possibility to do what you want - the 'strict-dinamic' token paired with 'nonce-value' or 'hash-value'. But Safari still does not implemented this (it support by Chrome, Edge and Firefox). If you have CSP with 'nonce-value' like:

 script-src 'nonce-SomeSecureValue' 'strict-dinamic';

than:

 <script src='https://domain/script.js' nonce='SomeSecureValue'>

will be allowed to load and any its child scripts it inserts will be allowed too.

In case CSP with 'hash-value':

 script-src 'sha256-HashOfScriptAllowed' 'sha256-HashOfScript2Allowed' 'strict-dinamic';

than:

 <script src='https://domain/script.js' integrity='sha256-HashOfScriptAllowed'>

will be allowed to load and any its child scripts it inserts will be allowed too. But Firefox has a bug and does support 'hash-value' for inline scripts only, not for external.

Alternatively you can use inline <script nonce='SomeSecureValue'> dynamically create script tags and load external scripts</script> and allow it via nonce or hash. All its child scripts will be allowed.

The Safari's 'strict-dynamic' bug can be bypassed using Google's strict CSP.

Why Inline Javascript is executed without error when Content-Security-Policy header is defined but not inline-script directive

i've put this small PHP file on my local xampp for testing...

first two notations are blocking the alert and the last allows it.
Tested on

  • Chrome v103.0.5060.114 x64
  • Edge 103.0.1264.49 x64
  • Firefox 102.0.1 x64.

All browsers create an error message on the console that the inline execution of a script has been blocked because of CSP.

<?php
// no alert
//header("Content-Security-Policy: script-src 'self' http: https:", true);
// no alert
header("Content-Security-Policy: script-src 'self'", true);
// alert
//header("Content-Security-Policy: script-src 'self' 'unsafe-inline'", true);
?><html>
<head><title></title></head>
<body>
<script>alert('test');</script>
</body>
</html>

Troubleshooting ideas:

  • Check the console if there CSP header is recognized or if there are unprintable chars or other syntax errors
  • Are there browser addons that are modifying incoming headers
  • Is there a security software running that is modifying incoming headers
  • Is a proxy server blocking the headers

Content Security Policy invalid characters

Yes, you have an error. You forgot a ";" before script-src:

Header always set Content-Security-Policy "default-src 'self'; script-src: 'self' 'unsafe-inline';"


Related Topics



Leave a reply



Submit