Content Security Policy - The Reality
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 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.
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.
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:
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
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:
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.
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
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.
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 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.