For a long time, web browser security was based on the Same-origin policy. What this policy says is that since web browsers implicitly trust any content that shows up from a requested domain, they will block requests from within this content to load scripts on other domains. But there have been ways around this, as I recently found out, via cross-site scripting (XSS) attacks. In a typical XSS attack, an attacker can use a medium you usually trust to inject malicious code which the browser might be forced to execute. CSP was designed to prevent this from happening.

The CSP header is a no-nonsense header that is designed to help you define a whitelist of trusted scripts and domains. The browser will then only load resources from these trusted domains, while blocking requests from others. CSP also provides a mechanism to provide a URL where the browser will report policy violations, so you can keep track of where malicious content is coming from and take steps accordindly. This allows you to debug your CSP policies and identify new and emerging threats. CSP will also block inline Javascript, Javascript with <script></script> tags, and eval()s.

Currently, this header has the following options:

An example CSP policy in Nginx might look like this:

add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; child-src 'self'";

Let’s try this out.

Apply the following CSP policy to your server block:

add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; child-src 'self'";

Then, upload the following HTML file to your Nginx document root:

<html>
<head>
    <script src="myjs.js"></script>
    <script src="https://localhost:444/myjs.js"></script>;
    <script src="https://evil.domain.com/myjs.js"></script>;
</head>
<body>
    Hello, World!
    <script>
        alert("This is some evil stuff...");
    </script>
</body>
</html>

I’m using this test setup with SSL on port 444, but yours will be different. Also, create and empty Javascript file by the name of myjs.js in the same folder.

Once this is done, try viewing the page using https:// in your browser. In Chrome’s developer tools, in the Console section, you should see this:

HTTP security header Content-Security-Policy Console tab output in Chrome.
Enlarge HTTP security header Content-Security-Policy Console tab output in Chrome.

If you see above, there are two policy violations: one for the Javascript we tried to load from evil.domain.com, and the other from the inline <script></script> we tried to load in the page. CSP correctly blocked both. If you need to allow inline Javascripts, then you need to specify unsafe-inline in your CSP policy. This will allow inline Javascript to execute, which is not very secure, but it will allow you to have CSP control other security aspects while you figure out how to remove inline Javascript from your code. Which is a good idea.

If you also see the Network tab in developer tools, you’ll see that the Javascript from the evil.domain.com was blocked, and the reason was clearly listed as blocked:csp.

HTTP security header Content-Security-Policy Network tab output in Chrome.
Enlarge HTTP security header Content-Security-Policy Network tab output in Chrome.

There is a lot more to read on CSP, including on how you can relax some of its controls to block a few things but allow some others while you’re testing out a new CSP. It’s reporting feature is especially useful if you want to capture policy violations on your end to debug potential policy misconfigurations and to know when bad guys are trying to infiltrate or probe your system.

Resources