Improving security of your web applications with the Content Security Policy

Hello my dear friends.

Today we will talk about Content Security Policy and how it can help your to improve security of your web applications.

What is Content Security Policy?

Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement or distribution of malware.

Usage example

In many cases many web applications do not need CSP, if they do not store and show some HTML/CSS data that user inputs. But in case when your web application should show some custom user content (html pages with css, some files, etc), you need to have good filter engine, which will remove any inline JavaScript code (<script> tags, onclick from <a> links, etc). For example, if you build web mail client, you cannot remove all HTML from email tags, because email will be broken and customer will not be happy to read broken email. So you need to prevent JavaScript inline code injection by this HTML email for hackers with any posibility.

How to use it

Instead of blindly trust to everything that a server delivers, CSP defines the Content-Security-Policy HTTP header that allows you to create a whitelist of sources of trusted content, and instructs the browser to execute or render only resources from those sources. Even if an attacker can find a hole through which to inject script, the script won’t match the whitelist, and therefore won’t be executed.

For example, if we trust cdn.example.com to deliver valid code, and we trust ourselves to do the same, let’s define a policy that only allows script to execute when it comes from one of those two sources:

Content-Security-Policy: script-src 'self' cdn.example.com

This head can contain such directives:

Directive Example Value Description
default-src 'self' cdn.example.com The "default-src" is the default policy for loading content such as JavaScript, Images, CSS, Font's, AJAX requests, Frames, HTML5 Media. See the Source List for possible values
script-src 'self' js.example.com Defines valid sources of JavaScript
style-src 'self' css.example.com Defines valid sources of stylesheets
img-src 'self' img.example.com Defines valid sources of images
connect-src 'self' Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If it is not allowed, the browser emulates a 400 HTTP status code
font-src font.example.com Defines valid sources of fonts
object-src 'self' Defines valid sources of plugins, eg <object>, <embed> or <applet>
media-src media.example.com Defines valid sources of audio and video, eg HTML5 <audio>, <video> elements
child-src (old version frame-src) 'self' Defines valid sources for loading frames
sandbox allow-forms allow-scripts 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: allow-forms allow-same-origin allow-scripts, and allow-top-navigation
report-uri /report Instructs the browser to POST a 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)

Source List

All of the directives that end with “-src” support similar values known as a source list. Multiple source list values can be space seperated with the exception of “*” and “none” which should be the only value.

Source Value Example Description
* img-src * Wildcard, allows anything
'none' object-src 'none' Prevents loading resources from any source
'self' script-src 'self' Allows loading resources from the same origin (same scheme, host and port)
data: img-src 'self' data: Allows loading resources via the data scheme (eg Base64 encoded images)
domain.example.com img-src img.example.com Allows loading resources via the data scheme (eg Base64 encoded images)
*.example.com img-src *.example.com Allows loading resources from the any subdomain under example.com
https: img-src https: Allows loading resources only over HTTPS on any domain
'unsafe-inline' script-src '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)
'unsafe-eval' script-src 'unsafe-eval' Allows unsafe dynamic code evaluation such as JavaScript eval()

Usage example

As you can see we can combine all this values for “Content Security Policy” header and create most flexible rule for our app. Let’s create a little example. I am using Rails app with global gem to make it work with “Content Security Policy”. First I create global yml file with configuration (config/global/content_security_policy.yml):

default:
  enabled: false
  default_src: "*"
  script_src: "'self' localhost:3000 localhost:9292"
  object_src: "'self'"
  style_src: "'self' 'unsafe-inline' 'unsafe-eval'"
  img_src: "* data:"
  media_src: "'none'"
  child_src: "'self'"
  font_src: "'self' data:"
  connect_src: "'self' ws://localhost:9292"

development:
  enabled: true

And user default_headers inside rails app (config/application.rb):

require File.expand_path('../boot', __FILE__)

require 'rails/all'

require File.expand_path('../../lib/loaderio_redis_config', __FILE__)

Bundler.require(:default, Rails.env)

# global initialize
Global.configure do |config|
  config.environment = Rails.env.to_s
  config.config_directory = File.expand_path('../global', __FILE__)
  config.namespace = "Global"
end

module YourCoolApp
  class Application < Rails::Application
    secure_headers = {
      'X-Frame-Options' => 'SAMEORIGIN',
      'X-XSS-Protection' => '1; mode=block',
      'X-Content-Type-Options' => 'nosniff'
    }

    if Global.content_security_policy.enabled?
      secure_headers.merge!(
        'Content-Security-Policy' => "default-src #{Global.content_security_policy.default_src}; script-src #{Global.content_security_policy.script_src}; object-src #{Global.content_security_policy.object_src}; style-src #{Global.content_security_policy.style_src}; img-src #{Global.content_security_policy.img_src}; media-src #{Global.content_security_policy.media_src}; child-src #{Global.content_security_policy.child_src}; frame-src #{Global.content_security_policy.child_src}; font-src #{Global.content_security_policy.font_src}; connect-src #{Global.content_security_policy.connect_src}"
      )
    end

    config.action_dispatch.default_headers = secure_headers

    ...

After restarting of the Rails app you should see “Content Security Policy” header in any HTTP response from your app. If someone will try to inject JS code in your app (onclick in link), it will get such JS error:

CSP error

Subresource Integrity

Many sites uses a content delivery network (CDN) to serve static assets such as JavaScript, CSS, and images to our users. The CDN makes web browsing faster by delivering assets from data centers that are geographically close to the end user and by using hardware and software that is optimized for quickly serving static assets. The compromise of a major CDN could be devastating to the security of the hundreds of thousands of sites that depends on it. If our CDN were to be compromised, it could be used to serve malicious JavaScript to all our users, rendering our many XSS mitigations and transport security useless. Content Security Policy is invaluable for protecting against traditional XSS attacks, but it provides no defense against an attacker who can control assets served from whitelisted sources.

To prevent this type of attack, you can use Subresource Integrity browser technology. The website author includes an integrity attribute on JavaScript and CSS tags, specifying the cryptographic digest of the resource being loaded from the third party. When the browser fetches the resource, it computes the file’s digest and compares it with the value from the integrity attribute. If the values match, the resource is loaded. Otherwise, the browser refuses to load the resource. Example:

<script src="/assets/application-asdhhwheruhsjkadlslkdl.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lsd29qs="></script>

If you are using Rails with sprockets-rails gem (version >= 3), you can add integrity key to your javascript_include_tag helper to activate this feature:

javascript_include_tag :application, integrity: true
# => "<script src="/assets/application.js" integrity="sha-256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>"

More info about Subresource Integrity you can read in this article.

Browser Support

CSP is designed to be fully backward compatible; browsers that don’t support it still work with servers that implement it, and vice-versa. Browsers that don’t support CSP simply ignore it, functioning as usual, defaulting to the standard same-origin policy for web content. If the site doesn’t offer the CSP header, browsers likewise use the standard same-origin policy.

Header Chrome FireFox Safari Internet Explorer/Edge
Content-Security-Policy (1.0) 25+ 23+ 7+ -
X-Content-Security-Policy - 4.0+ - 10+ (limited)
X-Webkit-CSP 14+ - 6+ -

As you can see in this table, CSP have good support for major browsers. Internet Explorer 10-11 and Edge have partial support for CSP via the X-Content-Security-Policy header, but even then they only appear to support the optional “sandbox” directive. More info on caniuse.

Summary

Content Security Policy can provide the additional security layer for your apps against XSS and data injection attacks (XSS is in third place in the ranking of the key risks of Web-based applications under the 2013 OWASP).

That’s all folks! Thank you for reading till the end.

Published:

October 13 2015