Using Content Security Policy (CSP) to secure web applications

Content Security Policy (CSP) is a computer security standard that provides an added layer of protection against Cross-Site Scripting (XSS), clickjacking, and other client-side attacks that rely on executing malicious content in the context of a trusted web page. This article shows how to use CSP headers to protect websites against XSS attacks and other attempts to bypass same-origin policy.

Using Content Security Policy (CSP) to secure web applications

Content Security Policy (CSP) is a security standard that allows you to prevent many types of code injection attacks, including cross-site scripting (XSS), clickjacking, and other attacks that involve executing malicious content in the context of a web page that is trusted by the browser. By putting the right CSP directives in HTTP response headers, you can precisely specify and limit data sources that are permitted by your website or application. Learn how to use CSP headers to protect your sites against XSS and other attacks that attempt to bypass same-origin policy.

Why do you need to use CSP?

Much of web application security is based around same-origin policy (SOP)—a fundamental principle that prevents sites from accessing data outside their origin. (For SOP, having the same origin means having the same domain, protocol, and port.) Enforcing this would be quite enough to secure sites from malicious external content, but it would also break the modern web, which requires sites to include lots of assets from external sources. Open any modern site in Dev Tools and look at everything that gets loaded: scripts and other resources from content delivery networks (CDNs), Google Analytics scripts, fonts, styles, comment modules, social media buttons… the list goes on. 

At the same time, malicious hackers continue to use cross-site scripting (XSS) attacks to trick websites trusted by the user into delivering malicious code. Browsers execute all code from a trusted origin and can’t tell which code is legitimate, so without additional safety measures related to SOP, any injected malicious code would be trusted and executed as well.

This is how we get to Content Security Policy (CSP)—a standardized set of directives that tell the browser which content sources to trust and which to block. Using carefully defined policies, you can restrict the types of content the browser accepts to eliminate many common injection vectors and significantly reduce the risk of XSS attacks. CSP also provides an extra layer of security by enforcing modern script coding styles.

CSP history and browser support

Content Security Policy is a recommendation of the W3C working group on web application security. Version 1 (aka Level 1) was proposed in 2012, with Level 2 following in 2014, and Level 3 as the current draft recommendation. While only a recommendation, CSP was quickly implemented by browser vendors, starting with Firefox 4. The majority of modern browsers support all or nearly all Level 2 directives, so this article describes CSP Level 2 as the current de facto standard.

Historically, CSP implementations have used three different content security policy header names, though only one remains in use today:

  • Content-Security-Policy: Standard header name recommended by W3C and used by all modern implementations and currently the only header to use.
  • X-WebKit-CSP (deprecated): Experimental header used in the past by Chrome and other WebKit-based browsers.
  • X-Content-Security-Policy (deprecated): Experimental header used in the past by browsers based on Gecko 2.

Using CSP directives

CSP allows you to define a variety of content restrictions using directives that are typically specified in HTTP response headers. Here’s an example of adding CSP headers for an Apache web server:

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

When added to the httpd.conf or .htaccess file, this line will set a default policy to allow only content from the current origin.

If needed, you can also provide specific directives at page level using HTML meta tags. Here’s an example that sets the same policy as above but using a meta tag:

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

Each directive consists of a name followed by one or more values and ends with a semicolon. You can use the * wildcard to match values, subdomains, schemes (protocols), and ports:

Content-Security-Policy: default-src *://*.example.com

This header would allow sources from any subdomain of example.com (but not example.com itself) using any scheme (http, https, etc.)

The official W3C recommendation contains a complete list of directives, but the following overview should give you a good idea of the most commonly used ones.

Source whitelisting directives

The main purpose of CSP is to filter out web content sources, so there are many directives for specifying valid sources for various types of assets. Once a Content-Security-Policy header is specified, the browser will reject content from sources that are not explicitly allowed using any of the directives below. Source values are separated by spaces and can include both URLs and the special keywords 'none', 'self', 'unsafe-inline', and 'unsafe-eval'. Importantly, each directive can be specified only once in the same header, and keywords must be entered in single quotes:

  • default-src is a fallback directive used to specify the default content policy for sources not covered by other directives. Common values include default-src 'self' to allow content from the current origin (but not its subdomains) and default-src 'none' to block everything that’s not explicitly whitelisted elsewhere.
  • script-src is used to whitelist script sources. To only allow scripts from the current origin, use script-src 'self'.
  • style-src is used to whitelist CSS stylesheet sources. To only allow stylesheets from the current origin, use style-src 'self'.
  • connect-src specifies permitted origins for direct JavaScript connections that use EventSource, WebSocket, or XMLHttpRequest objects.
  • object-src allows control over the sources of plugins such as Flash. (Note that the related plugin-types directive is now deprecated.)
  • img-src lets you restrict image sources.
  • font-src specifies permitted sources for loading fonts.
  • media-src restricts origins for loading sound and video resources.
  • child-src is used to restrict permitted URLs for JavaScript workers and embedded frame contents, including embedded videos. In Level 3, you can control embedded content and worker processes separately using the frame-src and worker-src directives, respectively.
  • frame-ancestors restricts URLs that can embed the current resource in <iframe>, <object>, and similar elements. This is a vital directive for preventing clickjacking attacks.

Writing JavaScript and CSS with CSP in mind

Using inline code is bad for security. Because it always executes in the current context, inline code can’t be restricted based on origin and is thus a major injection vector. When CSP is enabled, it blocks all inline code by default. This means no inline styles or inline scripts, including things like inline event handlers and javascript: URLs. To comply with CSP, all new code should follow the security best practice of using only external script and style files. To avoid breaking legacy applications, the unsafe-inline keyword is available to allow inline code for all or some script sources, but the W3C recommends avoiding it wherever possible.

For example, an old-style HTML and JavaScript page might contain script code all over the place, including in <script> tags and inline event handlers:

<script>function performButtonAction() {
   alert('You clicked the button!');}
</script>
<button onclick='performButtonAction();'>
   Want to click the button?
</button>

To refactor this to a modern and also CSP-compliant coding style, you should put the script in a separate file and use HTML purely declaratively. The two files would separate the declarative HTML:

<!-- buttonexample.html -->
<script src='buttonexample.js'></script>
<button id='examplebutton'>Want to click the button?</button>

from the actual script:

// buttonexample.js
function performButtonAction() {
   alert('You clicked the button!');
}
document.addEventListener('DOMContentLoaded', function () {
   document.getElementById('examplebutton')
      .addEventListener('click', performButtonAction);
});

Even assuming those typical injection vectors have been blocked, attackers might still achieve script execution if a site uses dynamically evaluated code. This is why enabling CSP also blocks all string evaluation functionality by default, including eval(), new Function([string]), setTimeout([string]), and similar constructs. This enforces several changes to coding practices, including the use of JSON.parse() instead of eval() for parsing JSON data.

Similar to unsafe-inline, you can use the unsafe-eval keyword for backwards compatibility to allow code evaluation for some or all origins. Again, this is against best practice for modern code, and should only be used for legacy code that can’t be refactored.

Allowing inline scripts using nonces and hashes

If you absolutely need to use some legacy inline code that can’t be moved out to separate file, CSP provides two features to safely allow only specific code blocks without resorting to a global unsafe-inline. To whitelist a specific piece of code, you can use either a nonce (unique one-time identifier) to identify and allow only a specific <script> tag or provide a cryptographic hash corresponding to the code itself. You can then specify the nonce or hash in your script-src directive to allow that piece of inline code.

As the name indicates, a nonce is a “number used once,” so it must be randomly generated for each page load. To use a script nonce, put it in the nonce attribute of a <script> tag and also add it to the script-src directive, prefixing the value with nonce-, for example:

<script nonce="uG2bsk6JIH923nsvp01n24KE">
   alert('Hello from Invicti');
</script>

To allow this specific script tag, use:

Content-Security-Policy: script-src 'nonce-uG2bsk6JIH923nsvp01n24KE'

Of course, this approach needs a way to regenerate and update the nonce for every page load, which can be troublesome. Another approach is to use a cryptographic hash of the permitted code itself. To do this, start by calculating the SHA hash of all characters inside the <script> tag (but without the tag itself). Then you can specify the hash value in the script-src directive, prefixing it with sha256-, sha384-, or sha512-, depending on the algorithm used. Here’s the same example but without the nonce attribute:

<script>alert('Hello from Invicti');</script>

The directive for allowing this code via an SHA256 hash would then be:

Content-Security-Policy: script-src 'sha256-f184cc7e1ab4bd53d5e217bcb22ad5a7de2833f03ee4957553af43ea5626bb12'

Page-level CSP directives

The main purpose of CSP is to whitelist content sources, but you can also use it to restrict actions that the current page can take. To use this functionality, use the sandbox directive to treat the page as though it was inside a sandboxed iframe. For a full description of restrictions applied by sandboxing a page, see Sandboxing in the HTML5 spec.

To improve security for older websites with lots of legacy HTTP pages, you can use the upgrade-insecure-requests directive to rewrite insecure URLs. This instructs user agents to change HTTP to HTTPS in URL schemes and can be invaluable when you still have many HTTP URLs.

Testing policies and monitoring violations

Content Security Policy is a powerful tool to control content sources and page behaviors, but this power also means that a single misconfigured directive could render your site partly or completely inaccessible to visitors. To prevent that, you need a way to safely test directives. 

Before you go live with your CSP directives, you can use the Content-Security-Policy-Report-Only header instead of Content-Security-Policy. In report-only mode, the browser will monitor and report policy violations but without actually enforcing restrictions. Use the report-uri directive to tell the browser where it should post violation reports in JSON format. This can be any local or external URI, for example:

Content-Security-Policy-Report-Only: default-src 'self'; ...; 
    report-uri /your_csp_report_parser;

Usefully, you can combine Content-Security-Policy-Report-Only and Content-Security-Policy headers to test a new policy while still enforcing an existing one.

Once a policy is live, you can use the same report-uri directive to get detailed reports about policy violations. Each JSON report starts with the csp-report attribute and looks something like this:

{
  "csp-report": {
    "document-uri": "http://invicti.com/index.html",
    "referrer": "http://nasty.example.com/",
    "blocked-uri": "http://nasty.example.com/nasty.js",
    "violated-directive": "script-src 'self' https://apis.google.com",
    "original-policy": "script-src 'self' https://apis.google.com; 
        report-uri http://invicti.com/your_csp_report_parser"
  }
}

The reports provide information about each policy violation, including the blocked URI and violated directive. This makes troubleshooting much easier, especially once you have policies with hundreds of directives and values.

Content Security Policy verification in Invicti security checks

CSP Level 2 has been implemented in all modern browsers and is widely used across the web as a best practice and an effective way of reducing the risk of XSS. To reflect this, Invicti vulnerability scans include checks for the presence of Content-Security-Policy HTTP headers and reports a “Best Practice” security issue if they are missing. Similar checks are performed for other recommended HTTP security headers.

However, merely having the CSP header is not enough. Invalid directives will be ignored by browsers (and therefore be ineffective), while unsafe directive values won’t provide the expected level of protection. To cover this, Invicti not only checks for the present of headers but also runs over 20 detailed checks to ensure that directives use correct syntax combined with values that provide effective security. See the vulnerability index for the current list of CSP-related checks available in Invicti.

Demo of Content Security Policy in action

For a practical demonstration of configuring CSP headers, watch this Security Weekly interview with Invicti Staff Security Engineer Sven Morgenroth. Talking to Paul Asadoorian, Sven presents the problems that CSP is designed to solve and goes on to do a hands-on demonstration of CSP headers in action.

Frequently asked questions

What is Content Security Policy (CSP)?

Content Security Policy is a security standard that helps prevent various types of attacks, such as cross-site scripting (XSS) and data injection, by controlling the sources from which content can be loaded on a web page.
 
Learn more about cross-site scripting (XSS) vulnerabilities.

How does a Content Security Policy (CSP) enhance web security?

A Content Security Policy enhances web security by mitigating risks associated with malicious content injection, unauthorized script execution, and data theft, thereby providing a layer of defense against common web-based attacks.
 
Read about other security headers you can use to improve web security.

How can organizations implement and maintain an effective Content Security Policy (CSP) for website protection?

Organizations can implement and maintain an effective Content Security Policy by carefully crafting policy directives tailored to their web applications, regularly monitoring and updating the policy to adapt to changing threats, and using vulnerability scanners and other CSP reporting mechanisms to identify and address security violations.
 
Read about web security frameworks that should include CSP as one of the controls.

Zbigniew Banach

About the Author

Zbigniew Banach - Technical Content Lead & Managing Editor

Cybersecurity writer and blog managing editor at Invicti Security. Drawing on years of experience with security, software development, content creation, journalism, and technical translation, he does his best to bring web application security and cybersecurity in general to a wider audience.