Content Security Policy - The Reality

csp

The Content Security Policy (CSP) is a powerful mechanism to prevent Cross Site Scripting (XSS) attacks on web sites which accounts for the majority of all security vulnerabilities.

But CSP is off to a slow start and is not implemented on the vast majority of web sites. Perhaps the difficulty implementing CSP is to blame?

This post examines a case study deploying CSP and has some recommendations for the social media companies to make it easier to implement CSP.

The Promise

The CSP promise is to prevent Cross Site Scripting vulnerabilities by defining a set of approved sources of content that a client browser can load. This enables the browser to block the integration of unauthorized content. One of the browser's primary defense mechanisms is the same origin policy, which stipulates that a web page may only download content (scripts, fonts, stylesheets, ...) from the same origin as the first web page. This creates a sand-box which isolates the web content and differentiates between valid authorized content and everything else. However, Cross Site Scripting bypasses this same origin policy by injecting malicious content with content from the original source. Unfortunately, it is all to easy for hackers to inject malicious code.

The Content Security Policy remedies this vulnerability by defining a white list of of approved URLs from which to download content. This is implemented via a HTTP Content-Security-Policy header that the application emits in the web response with the original web page. The browser examines this while list and blocks accesses to all sites not on the white list. For example:

Content-Security-Policy: default-src 'self' https://apis.google.com

This header permits access to content from the same origin ('self') and from apis.google.com.

The Delivery

When implemented correctly, CSP works well. It successfully blocks unauthorized content and prevents most Cross Site Scripting attacks. Consider an embedded device as part of the Internet of Things, perhaps a router with an embedded web user interface. All the defined content exists inside the router and no other web sites should be contributing content. For this, a restrictive CSP header value is both simple and effective.

Content-Security-Policy: default-src 'self'

This simple header can go a long way to reduce exposure to XSS attacks. Every embedded device should use this simple header. Unfortunately, implementing CSP on public web servers that require loading content from 3rd party sources is much more difficult, and therein lies the problem with CSP deployments.

The Problem

For CSP to work well, there are several important requirements:

  • You must know exactly what are the web site URLs from which your web site will incorporate content.
  • The hosting sites must document these URLs.
  • The host vendor must commit to not change these URLs.
  • The vendor script code must support also support CSP and not require the use of inline styles, scripts or eval().

Case Study: AeroNautic Blog Site

Let us consider a case study of http://AeroNautic.info which is a blog site that incorporates 3rd party content via the display of social media buttons for Facebook, Google+, YouTube and Twitter. It also uses Google Analytics to track site access. The social media buttons, and Google Analytics require the execution of scripts authored by the respective vendors. This is what the buttons look like when displayed:

buttons

To add the buttons to the web page, we add some HTML and script code generated by Facebook, Google and Twitter. This script code then accesses other resources from the vendor to download and render the button. Social media vendors provide developers with an online site to generate the HTML and script code to add to a web page. I tried to find if Facebook, Twitter, YouTube or Google document the required web sites to host the buttons and scripts, but was unable to find a documented list of URLs that will be accessed. None of these sites offer any documentation addressing what CSP headers are required to access their buttons or social media widgets. I also could not find any commitment not to change or modify the required URLs at will. The reason this matters is that if you deploy CSP and of these vendors changes a hosting site for any resource used by their scripts, your site will break as a consequence. CSP will prevent the downloading from a non-white listed site. CSP can break your site.

By reading the generated widget scripts, we can sleuth that the following URLs will be accessed.

https://www.google-analytics.com/analytics.js
https://twitter.com/share
https://platform.twitter.com/widgets.js
https://apis.google.com/js/platform.js

Enabling CSP

To enable CSP in our web page, we configure the web server to emit a Content-Security-Policy header. Read Content Security Policy - HTML5 Rocks for background on CSP directives. We add the following CSP header to white list our desired sites:

Content-Security-Policy: default-src 
    'self'
    www.google-analytics.com
    twitter.com
    platform.twitter.com/widgets.js
    apis.google.com/js/platform.js;

After refreshing the page, we expect to see our buttons but, .... nothing. Looking at the browser console log we see:

log1

CSP effectively blocked downloading content from unauthorized sites. The widgets are accessing additional, and undocumented web sites:

https://fonts.googleapis.com
http://connect.facebook.net
http://www.youtube.com
http://accounts.google.com

So, we rinse and repeat and add these sites to the CSP Header. Our CSP header now looks like this:

Content-Security-Policy: default-src 
    'self' 
    www.google-analytics.com 
    twitter.com
    platform.twitter.com 
    apis.google.com 
    fonts.googleapis.com 
    connect.facebook.net 
    www.youtube.com 
    accounts.google.com;

We inch forward, refresh the page, but still no buttons. Rather, we get a slew of new errors and unresolved sites.

log2

Yet more required (undocumented) sites:

fonts.gstatic.com
static.ak.facebook.com
s-static.ak.facebook.com
www.facebook.com
ssl.gstatic.com

So we add these sites and the new CSP header is:

Content-Security-Policy: default-src 'self' 
    www.google-analytics.com 
    twitter.com 
    platform.twitter.com     
    apis.google.com 
    fonts.googleapis.com 
    connect.facebook.net 
    www.youtube.com 
    accounts.google.com 
    fonts.gstatic.com 
    static.ak.facebook.com 
    s-static.ak.facebook.com 
    www.facebook.com 
    ssl.gstatic.com;

This results in 13 sites to enable 3 buttons and some analytics! We refresh the page and some buttons appear but not all.

Google Analytics Requires unsafe-inline

Having enabled the required sites, we turn to the rendering errors. The browser is showing errors relating to applying an inline style. It seems that Google Analytics and Twitter are not CSP friendly and are adding inline styles. Unfortunately this means we need to choose between disabling key parts of CSP and using Google Analytics. To enable Google Analytics, we need to add an unsafe-inline directive which creates a large back-door through the CSP defenses. This significantly erodes the protection we wanted from CSP in the first place.

We add 'unsafe-inline', refresh the browser and all the buttons appear. But we've disabled a key protection of CSP by enabling inline styles. This means a man-in-the-middle attack can inject inline styles to modify how our page looks and behaves.

Surprises: Why is doubleclick.net being accessed?

Just when we think we have our site working ... occasionally the YouTube widget is sneakily accessing the double click site. Our page has no advertising, only the youtube button. But is is attempting to load content from https://stats.g.doubleclick.net. This kind of access to undocumented sites is unacceptable for CSP to work effectively.

Refused to load the image 'https://stats.g.doubleclick.net/r/collect?v=1&aip=1&t=dc&_r=3&tid=UA-179169-5&cid=109512367.1445883942&jid=2140163724&_v=j39&z=1373107231' because it violates the following Content Security Policy

Other Examples:

While it may seem excessive to have the CSP header we required, here is the Gmail CSP header. It is almost 2K in length. CSP headers can be huge.

Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' https://hangouts.google.com/ https://talkgadget.google.com/ https://*.talkgadget.google.com/ https://www.googleapis.com/appsmarket/v2/installedApps/ https://www-gm-opensocial.googleusercontent.com/gadgets/js/ https://docs.google.com/static/doclist/client/js/ https://www.google.com/tools/feedback/ https://s.ytimg.com/yts/jsbin/ https://www.youtube.com/iframe_api https://ssl.google-analytics.com/ https://apis.google.com/_/scs/abc-static/ https://apis.google.com/js/ https://clients1.google.com/complete/ https://apis.google.com/_/scs/apps-static/_/js/ https://ssl.gstatic.com/inputtools/js/ https://ssl.gstatic.com/cloudsearch/static/o/js/ https://www.gstatic.com/feedback/js/ https://www.gstatic.com/common_sharing/static/client/js/ https://www.gstatic.com/og/_/js/;frame-src 'self' https://accounts.google.com/ https://apis.google.com/u/ https://apis.google.com/_/streamwidgets/ https://clients6.google.com/static/ https://content.googleapis.com/static/ https://mail-attachment.googleusercontent.com/ https://www.google.com/calendar/ https://calendar.google.com/calendar/ https://docs.google.com/ https://drive.google.com https://*.googleusercontent.com/docs/securesc/ https://feedback.googleusercontent.com/resources/ https://www.google.com/tools/feedback/ https://support.google.com/inapp/ https://*.googleusercontent.com/gadgets/ifr https://hangouts.google.com/ https://talkgadget.google.com/ https://*.talkgadget.google.com/ https://www-gm-opensocial.googleusercontent.com/gadgets/ https://plus.google.com/ https://wallet.google.com/gmail/ https://www.youtube.com/embed/ https://clients5.google.com/pagead/drt/dn/ https://clients5.google.com/ads/measurement/jn/ https://www.gstatic.com/mail/ww/ https://www.gstatic.com/mail/intl/ https://clients5.google.com/webstore/wall/ https://ci3.googleusercontent.com/ https://apis.google.com/additnow/ https://www.gstatic.com/mail/promo/;object-src https://mail-attachment.googleusercontent.com/swfs/ https://mail-attachment.googleusercontent.com/attachment/;report-uri /mail/cspreport

The Gmail CSP header uses unsafe-inline, unsafe-eval, 47 different domain URLs and 3 of these have wildcards. I wonder why they bothered using CSP. With such a large white list and permitting unsafe inline and eval, the CSP is much less effective than it should be. I think someone has lost the plot, there must be an easier way.

For comparison, here is the GitHub CSP Header:

Content-Security-Policy:default-src *; script-src assets-cdn.github.com collector-cdn.github.com; object-src assets-cdn.github.com; style-src 'self' 'unsafe-inline' 'unsafe-eval' assets-cdn.github.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com checkout.paypal.com collector.githubapp.com *.githubusercontent.com *.gravatar.com *.wp.com; media-src 'none'; frame-src 'self' render.githubusercontent.com gist.github.com www.youtube.com player.vimeo.com checkout.paypal.com; font-src assets-cdn.github.com; connect-src 'self' live.github.com wss://live.github.com uploads.github.com status.github.com api.github.com www.google-analytics.com api.braintreegateway.com client-analytics.braintreegateway.com github-cloud.s3.amazonaws.com; base-uri 'self'; form-action 'self' github.com gist.github.com

GitHub is also using unsafe-inline and unsafe-eval and has a large CSP header at 843 bytes.

Here is the Facebook CSP header:

content-security-policy:default-src * data: blob:;script-src *.facebook.com *.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* 'unsafe-inline' 'unsafe-eval' *.akamaihd.net *.atlassolutions.com blob: chrome-extension://lifbcibllhkdhoafpjfnlhfpfgnpldfl;style-src * 'unsafe-inline';connect-src *.facebook.com *.fbcdn.net *.facebook.net *.spotilocal.com:* *.akamaihd.net wss://*.facebook.com:* https://fb.scanandcleanlocal.com:* *.atlassolutions.com attachment.fbsbx.com 127.0.0.1:*;

Facebook is also using unsafe-inline and unsafe-eval. The header is 542 bytes.

AeroNautic Summary

To implement social media buttons and analytics, we had to discover the undocumented list of sites accessed by social media buttons and Google Analytics. This was an iterative process as the Facebook, Google and Twitter do not document the required sites. Futher, we needed to weaken CSP to permit unsafe-inline styles as the 3rd party code would not operate without it.

The AeroNautic site is currently working, but for how long? If any of Facebook, Twitter or Google modify the sites accessed by their script code, then the web site will fail to render the social media buttons. Confidence is low.

The problem

The AeroNautic exercise highlighted several problems with how vendors are supporting CSP that help to explain the low deployment percentage of CSP:

  • Vendors are not documenting the CSP site list required to run content. It is currently a voyage of discovery to determine the CSP site list. This seems to be an slow and iterative process.
  • Vendors are changing the site list over time that is required to run content. Vendors must demonstrate a commitment to have a stable site list. When using CSP, these sites are now part of the API and site list compatibility is an issue.
  • Vendors are requiring too many domains. They need to aggregate required content under a single domain. HTTP headers are a significant portion of download time and having very large CSP header impacts performance, especially for mobile.
  • Vendor scripts are not CSP friendly and are requiring unsafe-inline and unsafe-eval and thus defeating some of the key benefits of CSP.

The CSP Rules

With these issues, CSP is painful to implement when using 3rd party widgets and offers only marginal benefits due to the vendor CSP bypasses via unsafe-eval/unsafe-inline.For CSP to be widely deployed, there needs to be a rethink. I'd like to propose the following CSP Rules.

  • Vendors must fully document the complete URL list of sites required to deploy their widget code.
  • Vendors must maintain stable a stable URL site list as it is part of the widget API to support CSP.
  • Vendors must not require the use of unsafe CSP directives. This significantly weakens CSP.
  • Vendors should host all widget code on as few domains as possible, preferably on one single domain.
  • Vendors should document and generate CSP friendly script code that should be run from a separate script by default and not inline.

If these practices were adopted by vendors, then CSP would be far easier to implement and would be much more effective in practice.

A Bright Spot with the Internet of Things

While CSP has some real issues when deployed for web sites using social mediate widgets, it works extremely effectively in embedded devices for the Internet of Things. Devices typically have all their content locally on the device and so a restrictive CSP header provides very effective XSS protection.

Background Reading:

Dickson said ...

Great post, thanks mate !
December 11, 2015, 7:24 am

Mike Gale said ...

Great post, factual, detailed, meaningful. Highlights the odd things that advertising oriented "free" services do. To me this says that if you control just about everything on your site (maybe with some CDN) this can work for you.
January 5, 2016, 11:02 pm

Mike Gale said ...

One solution: Use it for sites where you don't load anything from these guys. If your site uses this stuff already, change it.
January 6, 2016, 1:42 am

Phil said ...

I've come across exactly this problem. Google analytics requiring unsafe-inline and unsafe-eval. Plus the hidden ads double click domain which I've left blocked in my csp. The inline and eval are seriously worrying as code injection was something I wanted to protect against with the help of a csp. It's only protecting against other non white listed third party domains which is a plus I suppose.
May 10, 2016, 7:40 pm

© Embedthis Software, 2003-2015. All rights reserved. Privacy Policy and Terms of Use.   Generated on Jun 2, 2016.