Security Headers: Content Security Policy

December 3, 2018

One of the most common (and most annoying) web application vulnerabilities is XSS (Cross Site Scripting). Depending on the context it can either be an irritation or downright dangerous. To defend against this vulnerability you have a few options; proper input/output escaping (something that should be happening anyway but everyone makes mistakes) or place a Web Application Firewall in the way (does slow down the application). What if there was a better way? A control that didn't rely on developers being forgetful or add extra overhead to the user experience. As a matter of fact there is such a way: Content Security Policy

The Content Security Policy (CSP) is an effective web application security control to prevent XSS and other injection attacks. CSP enacts certain "policies" that are sent to the user's browser. These policies act as a whitelist for where certain resources are allowed to come from.

How does it work?

When you load a webpage, you are downloading JavaScript, CSS, Fonts, Images, and all sorts of elements from other domains. What would happen if an attacker could somehow add their own JavaScript into the mix? It would give an attacker the opportunity to steal credentials, redirect users to a different website, and more! Content Security Policy provides a framework to choose which domains we will trust. This also includes the ability to reject inline JavaScript!

How to implement a CSP?

There are two ways to implement a Content Security Policy. The first (and preferred) method is to set it as a response header. The server will issue the CSP header with a "policy" as defined by the developer.

The second way to implement a CSP is to provide it as a "<meta>" tag in the html of the page. Please note that this is not the best way to implement the control, as the policy will not apply to "frame-ancestors", "report-uri", or "sandbox" directives.

In the situation where the CSP is defined as a response header and as a meta tag at the same time Chrome and Firefox will respect whichever policy is the most restrictive

When a CSP is defined as a response header it will take the following form

Content-Security-Policy: script-src 'self' https://maxcdn.bootstrapcdn.com

Here we are defining where JavaScript may be loaded from. Those two locations are "self" (which indicates the domain the page was served from) and "https://maxcdn.bootstrapcdn.com" which is the CDN for Bootstrap. If an attacker was able to either inject malicious JavaScript from a malicious domain, or inject inline JS, the browser would ignore it and throw an error in the browser console.

As you can see, this is a powerful tool in our Application Security Toolbox! And the good news for us is that it doesn't only apply to JavaScript! Content-Security-Policy can be used to restrict a variety of different elements and the origins they are loaded from!

content-security-policy.com provides a list of all the key directives and source options which I will display below.

Directives Reference List

  • default-src: The default-src is the default policy for loading content such as JavaScript, Images, CSS, Fonts, AJAX requests, Frames, HTML5 Media.
  • script-src: Defines valid sources of JavaScript.
  • style-src: Defines valid sources of stylesheets.
  • img-src: Defines valid sources of images.
  • connect-src: Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If not allowed the browser emulates a 400 HTTP status code.
  • font-src: Defines valid sources of fonts.
  • object-src: Defines valid sources of plugins, eg <object>, <embed> or <applet>.
  • media-src: Defines valid sources of audio and video, eg HTML5 <audio>, <video> elements.
  • frame-src: Defines valid sources for loading frames. "child-src" is preferred over this deprecated directive
  • sandbox: Enables a sandbox for the requested resource similar to the iframe sandbox attribute. The sandbox applies a same origin policy, prevents popups, plugins and script execution is blocked. You can keep the sandbox value empty to keep all restrictions in place, or add values.
  • report-uri: Instructs the browser to POST reports of policy failures to this URI. You can also append '-Report-Only' to the HTTP header name to instruct the browser to only send reports (does not block anything).
  • child-src: Defines valid sources for web workers and nested browsing contexts loaded using certain elements.
  • form-action: Defines valid sources that can be used as HTML form actions.
  • frame-ancestors: Defines valid sources for embedding resources in frames and iframes.
  • plugin-types: Defines valid MIME types for plugins invoked via certain elements.

Sources Reference List

  • *: Wildcard, allows any URL except data: blob: filesystem: schemes.
  • 'none': Prevents loading resources from any source.
  • 'self': Allows loading resources from the same origin (same scheme, host and port).
  • 'data:': Allows loading resources via the data scheme (eg Base64 encoded images).
  • domain.example.com: Allows loading resources from the specified domain name.
  • *.example.com: Allows loading resources from any subdomain under example.com.
  • https://example.com: Allows loading resources only over HTTPS matching the given domain.
  • 'https:': Allows loading resources only over HTTPS on any domain.
  • 'unsafe-inline': Allows use of inline source elements such as style attribute, onclick, or script tag bodies (depends on the context of the source it is applied to) and javascript: URIs
  • 'unsafe-eval': Allows unsafe dynamic code evaluation such as JavaScript eval()
  • 'nonce-': Allows script or style tag to execute if the nonce attribute value matches the header value. For example: <script nonce="2726c7f26c">alert("hello");</script>
  • 'sha256-': Allow a specific script or style to execute if it matches the hash. Doesn't work for javascript: URIs.

Important Directives

The following are some important directives that we should point out

default-src

It is important to note that by default all policy directives are left open. For example, if we don't specify a list of safe origins for images, an image can be loaded from any domain. The way we get around this is through the "default-src" directive. As mentioned in Google's Web Development Fundamentals "Generally, [default-src] applies to any directive that ends with "-src"". By setting this directive, we are telling the browser to only load a variety of resources from a single domain (so long as you specify it).

report-uri

CSP is very effective at preventing injection attacks, but wouldn't you want to find and fix the underlying vulnerabilities/bugs? The "report-uri" directive can help in that regard. This directive will ask the browser to report any CSP violations it detects to a specific endpoint through POST requests. This can help you fix any misconfigurations that may exist in your web app.

Prevent Inline Elements

The greatest power of the CSP is its ability to block inline elements. For example, even if an attacker is able to inject JavaScript into a page it won't trigger if the Content Security Policy is configured correctly.

Unfortunately, many developers will want to still include <script> tags in their HTML to perform some kind of JavaScript. The solution (to make it compatible with CSP) would be to move that code to a separate file that is loaded later. While yes, this does increase the overhead in terms of fetching an additional resource, it will also improve the development process by separating functionality from the HTML document.

While having no inline code is ideal, sometimes there is no way around it. If your dev team cant (for whatever reason) remove the inline code, Content Security Policy does provide a simple workaround through cryptography. By taking the SHA256 (or SHA384, SHA512) hash of the JavaScript and adding this to the CSP response header, the inline JavaScript will be allowed to run. An example of this is found here.

There is a dangerous alternative to using hashes. Simply putting the 'unsafe-inline' source in the CSP will allow any inline scripts to run. This should be a last resort and not something implemented over the long term.

Application Security Enforcement

Another great feature of Content Security Policy is that you can ensure your developers are following a basic Application Security Principle: don't use 'eval'. When configured correctly CSP won't allow 'eval' or any similar dangerous functions to trigger. This behavior can be overwritten by the 'unsafe-eval' source.

CSP Report Only

As mentioned above, the reporting functionality is very beneficial for logging and alerting to issues. If you have a particularly complex app, it may be helpful to only implement the reporting and not actually restrict anything. That is where "Content-Security-Policy-Report-Only" comes in. This header wont block any resources but it will report all violations of the CSP.

This can be incredibly useful if you are planning to change your policies, as you can see ahead of time if anything will break.

References/Further Reading

  • https://content-security-policy.com
  • https://developers.google.com/web/fundamentals/security/csp
  • https://en.wikipedia.org/wiki/Content_Security_Policy
  • https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP