Web
Article

Improving Web Security with the Content Security Policy

By Ian Oxley

Content Security Policy (CSP) is a security mechanism that helps protect against content injection attacks, such as Cross Site Scripting (XSS). It's a declarative policy that lets you give the browser a whitelist of where it can load resources from, whether the browser can use inline styles or scripts, and whether it can use dynamic JavaScript evaluation—such as through the use of eval. If there's an attempt to load a resource from somewhere that isn't on this whitelist, loading of that resource is blocked.

How it Works

CSP is currently a Candidate Recommendation published by the W3C WebApplication Security Working Group. It's delivered to the browser via the Content-Security-Policy HTTP header, which contains one or more directives that whitelist domains from which the browser is allowed to load resources. CSP 1.0 has the following directives:

  • default-src
  • script-src
  • object-src
  • style-src
  • img-src
  • media-src
  • frame-src
  • font-src
  • connect-src

The default-src, as the name suggests, sets the default source list for the remaining directives. If a directive isn't explicitly included in the CSP header, it will fall back to using the values in the default-src list.

All directives follow the same pattern:

  • self is used to refer to the current domain
  • one or more URLs can be specified in a space-separated list
  • none indicates that nothing should be loaded for a given directive e.g. object-src 'none' indicates that no plugins—such as Flash or Java—should be loaded.

At its simplest, we could define a CSP to load resources only from the current domain as follows:

Content-Security-Policy:    default-src 'self';

If an attempt to load a resource from any other domain is made, it is blocked by the browser, and a message is logged to the console:

Browser console in Chrome

By default, too, CSP restricts the execution of JavaScript by disallowing inline scripts and dynamic code evaluation. This, combined with whitelisting where resources can be loaded from, goes a long way to preventing content injection attacks. For example, an XSS attack attempt to inject an inline script tag would be blocked:

Inline script blocked in Chrome

As too would any attempt to load an external script that wasn't included in the CSP:

External script blocked in Chrome

Paths aren't currently supported in the URLs, so you can't lock down your site to only serve CSS from http://cdn.example.com/css. You have to make do with specifying the domain only—for example, http://cdn.example.com. You can, however, use wildcards—for example, to specify all subdomains of a given domain, such as *.mycdn.com.

Subsequent directives don't inherit their rules from previous directives. Each directive you include in the CSP header must explicitly list the domains / subdomains it allows. Here default-src and style-src both include self, and script-src and style-src both contain http://cdn.example.com:

Content-Security-Policy:    default-src 'self'; 
                            style-src 'self' http://cdn.example.com; 
                            script-src http://cdn.example.com;

If you need to use data URLs for loading resources, you'll need to include data: in your directive—for example, img-src 'data:';.

Aside from listing domains, two further features supported by script-src and style-src are unsafe-inline and unsafe-eval:

  • unsafe-inline can be used by style-src and script-src to indicate that inline <style> and <script> tags are allowed. CSP uses an opt-in policy. That is, if you don't include unsafe-inline, then all inline <style> and <script> tags are blocked. unsafe-inline also permits inline style attributes for CSS, and permits inline event handlers (onclick, onmouseover etc.) and javascript: URLs (such as <a href="javascript:foobar()">).
  • unsafe-eval can be used by script-src. Again, it uses an opt-in policy, so if your script-src doesn't explicitly include unsafe-eval, any dynamic code evaluation—which includes the use of eval, the Function constructor, and passing strings to setTimeout and setInterval—is blocked.

Browser Support

Browser support for CSP 1.0 is pretty good, with Internet Explorer being the usual elephant in the room: IE10 and IE11 have partial support for CSP via the X-Content-Security-Policy header, but even then they only appear to support the optional sandbox directive.

Capturing CSP Violations with report-uri

I mentioned earlier that any violation of your CSP will be logged to the browser console. Whilst that may be fine when your site is under development, it's not really practical when you deploy your CSP to production.

Instead, you can use the report-uri to log all CSP violations. This directive takes a URL as its value, and makes an HTTP POST request to this URL when a CSP violation is detected. The request body contains a JSON object that is populated with details of the violation.

To illustrate this, suppose we have a CSP as follows:

Content-Security-Policy:    default-src 'self'; 
                            report-uri: https://example.com/csp/report;

This means the browser is only permitted to load resources from our own domain. However, our site uses Google Analytics, so it attempts to load JavaScript from
www.google-analytics.com. This violates our CSP, so the following JSON is submitted via a HTTP POST request to our report-uri:

{
    "csp-report": {
        "blocked-uri:" "http://ajax.googleapis.com"
        "document-uri:" "http://example.com/index.html"
        "original-policy": "default-src 'self'; report-uri http://example.com/csp/report"
        "referrer:" ""
        "violated-directive": "default-src 'self'"
    }
}

Content-Security-Policy-Report-Only

If you're thinking of implementing CSP, you can take your CSP for a dry run by using the Content-Security-Policy-Report-Only HTTP header instead of Content-Security-Policy. This works just the same way as the CSP header, but it only reports on violations without actually enforcing the policy by blocking restricted resources. You can even use both headers at the same time, enforcing one policy while monitoring the effect any changes might have in the other.

Setting the CSP HTTP Header

I mentioned earlier that CSP is sent an HTTP header. Setting HTTP headers can be done directly on the server in your server's configuration file(s):

# Apache config
Header set Content-Security-Policy "default-src 'self';"

# IIS Web.config
<system.webServer>
    <httpProtocol>
        <customHeaders>
            <add name="Content-Security-Policy" value="default-src 'self';" />
        </customHeaders>
    </httpProtocol>
</system.webServer>

# nginx conf file
add_header Content-Security-Policy "default-src 'self';";

Alternatively, many programming languages / frameworks support adding HTTP headers programmatically—such as PHP's header, or Node's setHeader—so you could use these to set your CSP header:

# PHP example
header("Content-Security-Policy: default-src 'self'");

# Node.js example
request.setHeader("Content-Security-Policy", "default-src 'self'");

CSP in the Wild

Let’s have a look at how Facebook and Twitter implement CSP. First, here's Facebook (line breaks added for readability):

default-src *;
script-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* 'unsafe-inline' 'unsafe-eval' https://*.akamaihd.net http://*.akamaihd.net *.atlassolutions.com;
style-src * 'unsafe-inline';
connect-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.spotilocal.com:* https://*.akamaihd.net wss://*.facebook.com:* ws://*.facebook.com:* http://*.akamaihd.net https://fb.scanandcleanlocal.com:* *.atlassolutions.com http://attachment.fbsbx.com https://attachment.fbsbx.com;

Note how Facebook makes use of wildcards for both subdomains, as well as port numbers in connect-src.

Here's the CSP used by Twitter:

default-src https:;
connect-src https:;
font-src https: data:;
frame-src https: twitter:;
frame-ancestors https:;
img-src https: data:;
media-src https:;
object-src https:;
script-src 'unsafe-inline' 'unsafe-eval' https:;
style-src 'unsafe-inline' https:;
report-uri https://twitter.com/i/csp_report?a=NVQWGYLXFVZXO2LGOQ%3D%3D%3D%3D%3D%3D&ro=false;

Notice how the directives all contain https:, thus enforcing SSL.

Changes Coming in CSP Level 2

CSP Level 2 is currently a Candidate Recommendation, and defines some new directives and security measures:

  • base-uri controls whether the document is allowed to manipulate the base URI of the page.
  • child-src replaces frame-src.
  • form-action controls the document's ability to submit HTML forms.
  • frame-ancestors works like the X-Frame-Options header, by controlling how this document can be embedded in other documents, and is in fact intended to replace this header.
  • plugin-types controls which specific plugins can be loaded by the page—such as Flash, Java, Silverlight, etc.

The JSON submitted to report-uri gets a couple of extra fields: effective-directive contains the name of the directive that was violated; and status-code, which contains the HTTP status code of the requested resource, or zero if the resource wasn't requested over HTTP.

CSP Level 2 also includes the ability for inline scripts and style sheets to be protected via nonces and hashes.

Protecting Inline Styles and Scripts Using a Nonce

A nonce is just a random string that's generated on the server, included in the CSP header, and also included on an inline script tag. So, our CSP header with a nonce will look something like this:

Content-Security-Policy:    default-src 'self';
                            script-src 'self' 'nonce-Xiojd98a8jd3s9kFiDi29Uijwdu';

When rendering the page, that same nonce needs to be included in the nonce attribute on the script tag for the inline script to execute:

<script>
    console.log("Script won't run as it doesn't contain a nonce attribute");
</script>

<script nonce="Eskdikejidojdk978Ad8jf">
    console.log("Script won't run as it has an invalid nonce");
</script>

<script nonce="Xiojd98a8jd3s9kFiDi29Uijwdu">
    console.log('Script runs as the nonce matches the nonce in the HTTP header');
</script>

Protecting Inline Styles and Scripts Using a Hash

To use this approach, first you compute the hash of the style or script block on the server and include this in the CSP header in the style-src or script-src respectively. Then the browser computes the hash of the style/script block before it renders the page. If the hash computed by the browser matches the one computed on the server, the style/script block is allowed to execute.

Here the CSP header includes the sha256-base64–encoded hash of the string console.log('Hello, SitePoint'); in the script-src directive:

Content-Security-Policy: 
                default-src 'self';
                script-src 'self' 'sha256-V8ghUBat8RY1nqMBeNQlXGceJ4GMuwYA55n3cYBxxvs=';

When the browser renders the page, it'll compute the hash of each inline script block and compare them against the whitelisted hashes sent in the CSP header. If it finds a match, the script executes. Note that whitespace is significant. This script will be fine:

<script>console.log('Hello, SitePoint');</script>

The hash of these inline scripts won't match the whitelisted value in the header, so none of them will execute:

<script> console.log('Hello, SitePoint');</script>
<script>console.log('Hello, SitePoint'); </script>
<script>console.log('Hello, World');</script>

Conclusion

In this article, we've had a look at CSP 1.0, and how you can use its directives to whitelist where your site can load resources from. We had a look at how you can use report-uri to collect information on requests that violate your CSP, and how Facebook and Twitter use CSP today. We finished up by looking at some of the changes that are coming in CSP 2.0—in particular, how you can get more protection for your inline styles and scripts using nonces and hashes.

If you have questions about CSP and how it works, please comment below. Have you given CSP a try yet? If so, how did it go?

  • Frankie Fajardo

    I quickly tried the web.config “default-src: ‘self'” setup and that caused Chrome to refuse loading a bundled js. And I had to add ‘unsafe-inline’, which confused me a bit since ‘unsafe-inline’ is for inline (not linked) content/script. Have you encountered that scenario? Did I misunderstand something?

    Also, as with MDN, I’m not sure if implicitly suggesting to use “default-src: ‘self'” is the way to go, as this applies to any policy directive you do not specify, which may not be what you want. I would rather start with script-src. What do you think?

    • ianoxley

      Hmm, it’s a bit tricky to comment without being able to see your code. Have you tried loading the unbundled JavaScript? That might give you a better indication of what was causing Chrome to not load the bundled JavaScript.

      And I can’t think of any reason why starting with script-src wouldn’t work. It might be less work than disabling those directives with ‘none’ that you don’t want to implicitly use the default-src policy e.g. default-src: ‘self’; image-src: ‘none’; connect-src: ‘none’;

      • Frankie Fajardo

        Hi ianoxley. Firstly, I’d like to apologize about my post. I mistyped “Did you misunderstand something” when I meant “Did *I* misunderstand something”. My fingers can type quicker than my thoughts sometimes. :P

        I discovered why Chrome (and FireFox) refuses to load my bundled script when I set my CSP to “default-src: ‘self'”. My bundled script includes jquery.autosize.js (v1.18.0). This has a line of code (line 26) for a hidden textarea, and the textarea tag has inline style. If I remove the textarea’s inline style, the bundled script loads with no CSP violation.

        I guess that goes back to my other point. Using a CSP of “default-src: ‘self'” and nothing else could break a lot of things unless you thoroughly check not just your scripts/resources, but also external libraries that you include in your project. It will have to be reviewed project by project, IMHO.

  • Adam

    I would like to use inline CSS.
    can you please tell me what code I have use instead of
    Header set Content-Security-Policy “default-src ‘self'”
    thanks

  • Adam

    I would like to use inline script and CSS.
    can you please tell me which code I need.
    this one doesnt work
    Header set Content-Security-Policy: “default-src ‘self’; script-src ‘unsafe-inline’ ‘unsafe-eval’; style-src ‘unsafe-inline'”

  • SunnY M

    In Apache, How can we tune this for following headers.

    Missing “Content-Security-Policy” header 11
    Missing “X-Content-Type-Options” header 11
    Missing “X-XSS-Protection” header 11

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Instant Website Review

Use Woorank to analyze and optimize your website to improve your website to improve your ranking!

Run a review to see how your site can improve across 70+ metrics!

Get the latest in Front-end, once a week, for free.