Improving Web Security with the Content Security Policy

Share this article

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:" "https://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?

Frequently Asked Questions (FAQs) on Content Security Policy

What is the purpose of a Content Security Policy (CSP)?

A Content Security Policy (CSP) is a security measure used in web development to prevent cross-site scripting (XSS), clickjacking, and other code injection attacks. It allows web developers to specify the domains that a browser should consider as valid sources of executable scripts. By restricting the sources of scripts, CSP helps to prevent malicious scripts from damaging your website or web application.

How does a Content Security Policy work?

CSP works by adding a special HTTP header to a webpage that provides a string of directives. These directives tell the browser which domains are approved and what type of resources (like scripts, images, stylesheets, etc.) are allowed to load on the page. If a resource violates the defined policy, the browser will block it and report the violation.

How can I implement a Content Security Policy?

Implementing a CSP involves adding the Content-Security-Policy HTTP header to your web page’s serving code. This header is followed by a series of directives that specify the approved sources of content for different types of resources. For example, the directive “script-src ‘self’ https://apis.google.com” allows scripts to be loaded from the same domain as the page and from apis.google.com.

What are the common directives used in a Content Security Policy?

Some common directives used in a CSP include ‘default-src’, ‘script-src’, ‘style-src’, ‘img-src’, ‘connect-src’, ‘font-src’, ‘object-src’, ‘media-src’, ‘frame-src’, and ‘sandbox’. Each directive controls a specific type of resource and specifies valid sources for it.

What is the ‘default-src’ directive in a Content Security Policy?

The ‘default-src’ directive is the fallback for most fetch directives. If you don’t specify a fetch directive, the browser will use the value of ‘default-src’. It helps to set a baseline policy for your content sources which can be further refined with more specific directives.

How can I test a Content Security Policy before implementing it?

Before implementing a CSP, you can use the ‘Content-Security-Policy-Report-Only’ HTTP header to monitor potential violations. This header instructs the browser to report CSP violations but not block the content. This way, you can fine-tune your policy without breaking your site.

What is the impact of a Content Security Policy on SEO?

A CSP does not directly impact SEO. However, by improving your site’s security, it can indirectly benefit your SEO. Google and other search engines prioritize secure websites in their rankings. Therefore, implementing a CSP can contribute to your site’s overall SEO strategy.

Can a Content Security Policy block inline scripts and styles?

Yes, a CSP can block inline scripts and styles. This is a common practice to prevent XSS attacks. However, you can allow them by using the ‘unsafe-inline’ keyword in your policy. Be aware that this can make your site more vulnerable to attacks.

How can I handle CSP violation reports?

You can handle CSP violation reports by setting the ‘report-uri’ or ‘report-to’ directive in your policy. This directive specifies the endpoint where the browser should send reports about policy violations.

Can a Content Security Policy protect against all types of attacks?

While a CSP provides a strong layer of protection against certain types of attacks, it is not a silver bullet. It is most effective against XSS and data injection attacks. However, it cannot protect against attacks that do not involve code injection, such as CSRF attacks. Therefore, a CSP should be used as part of a broader security strategy.

Ian OxleyIan Oxley
View Author

Ian Oxley has been building stuff on the Web professionally since 2004. He lives and works in Newcastle-upon-Tyne, England, and often attends local user groups and meetups. He's been known to speak at them on occasion too. When he's not in front of a computer Ian can be found playing guitar, and taking photos. But not usually at the same time.

CSPRalphMsecurityw3cxss
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week