An Introduction to Content Security Policy

For modern web applications, a Content Security Policy (CSP) provides an extra layer of control for developers in regards to what other resources' content can interact with and include. It is usually set as an HTTP response header, or occasionally as a <meta> element, and consists of one or more declarative directives. These directives allow developers to control from where resources like scripts, style sheets, and images can be fetched. They also control navigational aspects such as, to where content can be embedded and to where HTML forms can be submitted.

Ultimately, CSP serves as an in-depth mitigation technique to help limit the impact of, or prevent, Cross-Site Scripting (XSS) or Clickjacking attacks. CSP is something we here at Sigient try to take advantage of whenever we launch a new web property. This post will introduce the different types of directives and how they can be used. We'll then share some advice when it comes to crafting a CSP and introduce a small Ruby tool we use to help build CSP strings.

Fetch Directives

When crafting a CSP, fetch directives are the most commonly employed directive type. These type of directives end with -src and allow authors to control from where resources can be fetched. To use a fetch directive, authors must specify the directive type followed by one or more source values. Multiple directives can be specified and delimited with a semi-colon. Below is a quick overview of the most commonly used fetch directives types and what they do:

  • script-src defines sources for script elements and blocks the use of inline <script> elements, eval(), inline event handlers, and other code evaluation techniques
  • style-src defines sources for style sheet elements, blocks the use of inline <style> elements, the style attribute, and other dynamic style techniques
  • font-src controls from where font resources can be fetched from via @font-face CSS rules
  • img-src defines sources for image and favicon elements
  • frame-src defines sources for frame type elements
  • connect-src controls where XHR, Fetch, Event Source, and Web Socket connections can be made
  • worker-src controls from where Worker types can load scripts
  • child-src is like worker-src but includes frame type elements
  • default-src serves as a fallback when a directive is not supplied for a given resource type

When a default-src directive is specified, the limitations imposed by the above fetch directives are also invoked. This is to say, if a default-src directive is used, it is like using a style-src and a script-src (as well as all the other fetch directives); now inline <style> and <script> elements, the style attribute, and other content features are no longer possible.

Source Values

The source values can take several different forms depending upon the directive type. Some possible source values for fetch directives include the following.

Host Source

These values take the form of a URL and may include a protocol, domain, or port number. Sources can also use a wildcard as a leading domain to allow for matching against an arbitrary sub-domain. A wildcard can also be used for the port number. Some examples include:

default-src https://sigient.com
connect-src https://*.sigient.com https://sigient.com
img-src https://*.cloudfront.com
style-src https://sigient:443

Schema Source

Schema sources or protocol sources must end in a colon. Often https: or http: are used but these sources can also use data schemata such as data: and blob:. Some examples include:

default-src https:
img-src https: data: blob:
media-src mediastream:; object-src filesystem:

Self and None Source Values

Fetch directives can also use the 'self' or 'none' source values. The 'self' value will match the exact URL, including the protocol, domain, and port number from which the content was requested. This is useful if resources are being served through the same domain as the content. The 'none' value acts like an empty set, will match nothing, and is useful if you want to disable a resource type (like scripts or images) entirely. Note that, both of these source values require single quotes to function properly; for example:

img-src 'self'; font-src 'none'

Unsafe Source Values

One of the main goals of CSP is to limit attack vectors for Cross-Site Scripting. In order to limit these vectors, the use of the dynamically evaluated code, typically with eval(), as well as the use of inline <script> and <style> elements, are blocked. Use of these features can be explicitly enabled via the 'unsafe-eval' and 'unsafe-inline' source value values. For example:

script-src 'unsafe-eval' 'unsafe-inline'; style-src 'unsafe-inline'

Limiting the use of inline <script> and <style> elements is perhaps one of the most challenging aspects when introducing a CSP to a modern web application. Often we use inline <script> elements to bootstrap client-side code with data and state. We recommend avoiding 'unsafe-inline' when possible. Information relayed to client-side applications can be moved to <meta> elements, hidden form <input> elements, other DOM elements, data attributes, or an HTTP API call.

Preventing the use of inline styles via the style attribute is another challenging aspect of implementing CSP. In some cases, React and other modern JavaScript frameworks encourage the use of the inline style attribute. Great care must be taken when implementing a policy for a modern client-side application.

Additionally, some web applications may be indirectly relying on the use of eval() or new Function() via certain type of template libraries. Once again, we recommend avoiding the use of 'unsafe-eval'. Templates can be converted to server-side versions or new client-side templating techniques can be employed.

Nonce and Hash Source Values

If using inline <script> and <style> elements is unavoidable, the nonce or hash source values will enable content to contain these type of elements without having to rely on 'unsafe-inline'.

The nonce source value begins with the prefix of nonce- and is followed by a cryptographic nonce. A nonce is a randomly generated value that will not repeat. The CSP specification states that nonce source types should be 128-bits long and encoded as Base64. The content can then contain a corresponding <script> and <style> element which has its nonce attribute set to this random value. For example, suppose we have the following script-src directive:

script-src: 'nonce-5XxtHqdcqTPG-6UipgKuwQ'

Its corresponding <script> element would appear:

<script nonce="5XxtHqdcqTPG-6UipgKuwQ">
  console.log("Inline script");
</script>

The hash source value is similar to the nonce source value except, rather than using a secure random value a cryptographic hash function is used. The input to this function is the content of the inline <script> or <style> element. Supported hash functions include SHA256, SHA384 and SHA512. Much like the nonce source type, the hash source type is prefixed with the hash algorithm, a dash, and the hash function output result. For example, suppose we have the following inline <style> element:

<style>
  body {
    font-weight:bold;
  }
<style>

To enable use of this inline <style> element we need to add a style-src directive with a hash source type value, as such:

style-src 'sha256-wgsKtubzDuQqbUOxwVGv4T61V/XFCjaD91PjJw4HLM0=' 

Note that both of these source values should be contained in single quotes. Using the nonce and hash source types it is possible to securely use inline <script> and <style> elements. We strongly encourage the use of the hash source value over the nonce value when possible. However, using the nonce option is still preferred over relying on the 'unsafe-inline' value.

Strict Dynamic

The final source type is the 'strict-dynamic' source type which is meant to be used with either the nonce or hash source types covered in the preceding section and only works with the script-src directive type. Essentially, 'strict-dynamic' will allow an inline <script> element to load additional script content dynamically. Any additional content loaded by the inline script will be implicitly trusted. For example:

script-src 'sha256-pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=' 'strict-dynamic'

Note that 'strict-dynamic' is only supported by browsers that support CSP Version 3. The main goal of 'strict-dynamic' is to lower the burden of declaring many safe sources upfront for a web application. For more details on how to deploy this source value in a backwards compatible fashion, please refer to the 'strict-dynamic' section of the CSP Level 3 specification.

Navigation Directives

Next are navigation directives. Currently only two directives, form-actions and frame-ancestors, are widely supported. The form-actions directive controls to which destinations form elements can submit. The frame-ancestors directive controls where content can be embedded in an <iframe> element or other embed-type element. Both of these directives accept any valid host, schema, 'self', or 'none' source values just like fetch directives. In particular, using frame-ancestors with the 'none' source value will block content from being embeddable in an iframe or other embed-type element. Think of this directive as a more powerful version of the X-Frame-Options header.

Document Directives

The first document directive of note is the base-uri directive. This directive controls which values are allowed for the <base /> meta HTML element. This element is not often used but can be useful in certain types of injection attacks. The second document directive of interest is the plugin-types directive. This directive allows you to specify allowed MIME types for plugins, such as Flash. If you are not using Flash or any other third-party plugins on your site, plugin-types 'none' is a great way to disable plugins altogether and further limit the attack surface of your application.

Other Directive Types

Some other directives that are useful when creating a Content Security Policy are the block-all-mixed-content and upgrade-insecure-requests directives. The first directive, block-all-mixed-content, will block requests for all resource types using HTTP if the content is served over HTTPS. Modern browsers will generally block these insecure requests by default without using this directive and will produce a "mixed content" error in the process. This is a good thing though, because serving any resources over HTTP degrades the security of the entire page. It is still good to be explicit and use this directive when authoring a Content Security Policy.

Another directive, upgrade-insecure-requests, helps to prevent mixed content warnings from occurring and will upgrade insecure HTTP requests to their HTTPS equivalent. These requests will only succeed if the upstream server supports HTTPS. Recently, a well-known information security blogger Troy Hunt covered this problem and how to use CSP to remedy the issue in a post entitled "Disqus' mixed content problem and fixing it with a CSP". It's certainly worth checking to see how this mixed content problem can arise in the wild!

Crafting a Content Security Policy

Now that we have a clear understanding of what CSP directives are and how we can use them, let's cover some steps for how to craft a Content Security Policy!

  1. Identify Sources for Fetch Directives
    Determine from where resources are being included. Are you using a CDN or are most resources hosted on the same domain?

    For this website, we exclusively use AWS Cloud Front so a default-src directive for our Cloud Front domain quickly covers most resource types. The Network tab within the browser's developer tools can make it easier to identify from where different resources are loaded. If your CSP blocks access to a particular resource, the console will also contain a warning which can help troubleshoot CSP issues.

    Additionally, when implementing a CSP for the admin side of this site, we had to deal with our client-side application making requests to Amazon's S3. This is the case because image uploads are handled client-side instead of server-side. As such, we had to use a connect-src which accounted for this:
    connect-src 'self' https://*.s3.amazonaws.com

  2. Remove Inline Script and Style Element
    If your site has any inline <script> or <style> elements they should be removed. If you absolutely must use one or more of these types of elements use a hash or nonce source value type. In Ruby this can be done using Digest::SHA256.base64digest(input). Remember to mind the white space on your input! If a single bit is off for the input, what is actually contained within an inline element will yield a different hash value.

    When implementing a CSP for our site, we decided to keep our Google Analytics script as an inline script. Thus, to permit inclusion of this inline element, we had to add the following to our script-src directive source values:
    script-src 'sha256-iWgWzB2ZsGuz/C7X2wUEafPsaaUSaxr4bl9hmSykuOs='
    We also had to rewrite some of our admin client-side application to use <meta> elements instead of inline <script> elements. On some of our admin pages we had to include style-src 'inline-unsafe' within our policy so inline styles would work for some open source JavaScript plugins we use. It is also completely acceptable to use different policies for different areas of your site. Be explicit and drill down the specific sources a given piece of content requires rather than be overly permissive. Remember, by limiting sources for various resource types, we limit the attack surface of our web applications.

    Overall, the transition was rather easy and our site is now free from unsafe inline scripts!

  3. Additional Directives
    Decide if any additional directives are worth adding. For example, if content is served over HTTPS, but uses 3rd party embeds, you may want to use upgrade-insecure-requests to avoid a mixed content warning. Additionally, if your content is embedded, you may want to limit access with the frame-ancestors directive or prevent embedding entirely.

    For this site we opted to include upgrade-insecure-requests and block-all-mixed-content. We also added a frame-ancestors directive with the source value of 'none' in order to block our content from being embedded elsewhere.

Writing a Policy

Now that we've identified our sources, removed or determined hashes for inline elements, and have decided on any additional directives, we can now finally pull all the pieces together for our Content Security Policy. We recommend using a default-src directive in order to be as encompassing as possible with regards to the various resource types. For this site we opted to use a connect-src, default-src, and script-src fetch directive. The script-src contains the same sources with the additional hash source as mentioned above. We also included the upgrade-insecure-requests and block-all-mixed-content in our policy as well.

Below is this site's complete CSP. Notice how we include google-analytics.com for both the default-src and script-src directives. This is because Google Analytics will include a script from this domain as well as an image. You can verify this by using the Network tab of the developer's tools by looking for the Content-Security-Policy header!

connect-src 'self';
default-src https://d1d5l73rdgzsp3.cloudfront.net https://www.google-analytics.com;
script-src https://d1d5l73rdgzsp3.cloudfront.net https://www.google-analytics.com 'sha256-iWgWzB2ZsGuz/C7X2wUEafPsaaUSaxr4bl9hmSykuOs=';
base-uri 'self';
frame-ancestors 'none';
plugin-types 'none';
block-all-mixed-content;
upgrade-insecure-requests

Our policy is rather simple since our site does not have user-generated content. One recent blog post about CSP is Github's CSP Journey. The post does a great job of outlining various threats GitHub had to deal with when supporting user-supplied content like Wiki pages. It is interesting to note how their current policy first uses default-src 'none'. In doing so, they have opted for a very strict deny-first policy which has to explicitly grant access to the various resource types.

Using CspBuilder to Write CSPs

We love to open source code here at Sigient. While tackling the problem of writing CSPs we wrote a small builder library in Ruby, which is aptly dubbed CspBuilder. This library provides a chainable builder class and adds a few small features like wrapping Symbols in single quotes (because single quotes are required by some types of source values for fetch directives.) Using this library, this site's CSP is composed as such:

class ApplicationController < ActionController::Base
  before_action :set_csp_header
  
  CSP_HEADER = CspBuilder.new.
    connect_src(:self).
    default_src("https://#{ENV['CLOUDFRONT_DOMAIN']}", 'https://www.google-analytics.com').
    script_src("https://#{ENV['CLOUDFRONT_DOMAIN']}").
    script_src('https://www.google-analytics.com', :'sha256-iWgWzB2ZsGuz/C7X2wUEafPsaaUSaxr4bl9hmSykuOs=').
    base_uri(:self).
    frame_ancestors(:none).
    plugin_types(:none).
    block_all_mixed_content.
    upgrade_insecure_request.
    compile!
  
  def set_csp_header
    response.headers['Content-Security-Policy'] = CSP_HEADER
  end
end

For more details on CspBuilder, please refer to its repository and documentation!

In Closing

We hope this blog post helps developers understand what a Content Security Policy is and how it is used. The various directive types, like fetch directives, navigation directives, and others that deal with mixed content enable developers to take full control over how their content interacts with other resources. While we always strive to build vulnerability-free web applications, mistakes happen and flaws exist. As such, a CSP solid serves as your best defense against Cross-Site Scripting flaws and should be deployed and leveraged whenever possible.