Content Security Policy
We’ve started rolling out a new security feature called “Content Security Policy” or CSP. As a user, it will better protect your account against XSS attacks. But, be aware, it…
We’ve started rolling out a new security feature called “Content Security Policy” or CSP. As a user, it will better protect your account against XSS attacks. But, be aware, it may cause issues with some browser extensions and bookmarklets.
Content Security Policy is a new HTTP header that provides a solid safety net against XSS attacks. It does this by blocking inline scripts and limiting the domains that other scripts can be loaded from. This doesn’t mean you can forget about escaping user data on the server side, but if you screw up, CSP will give you a last layer of defense.
Preparing your app
CSP header
Activating CSP in a Rails app is trivial since it’s just a simple header. You don’t need any separate libraries; a simple before filter should do.
before_filter :set_csp
def set_csp
response.headers['Content-Security-Policy'] = "default-src *; script-src https://assets.example.com; style-src https://assets.example.com"
end
The header defines whitelisted urls that content can be loaded from. The script-src
and style-src
directives are both configured to our asset host’s (or CDNs) base URL. Then, no scripts can be loaded from hosts other than ours. Lastly, default-src
is a catch-all for all other directives we didn’t define. For example, image-src
and media-src
can be used to restrict urls that images, video, and audio can loaded from.
If you want to broaden your browser support, set the same header value for X-Content-Security-Policy
and X-WebKit-CSP
as well. Going forward, you should only have to worry about the Content-Security-Policy
standard.
As CSP implementations mature, this might become an out of the box feature built into Rails itself.
Turning on CSP is easy, getting your app CSP ready is the real challenge.
Inline scripts
Unless unsafe-inline
is set, all inline script tags are blocked. This is the main protection you’ll want against XSS.
Most of our prior inline script usage was page specific configuration.
<script type="text/javascript">
GitHub.user = 'josh'
GitHub.repo = 'rails'
GitHub.branch = 'master'
</script>
A better place to put configuration like this would be in a relevant data-*
attribute.
<div data-user="josh" data-repo="rails" data-branch="master">
</div>
Inline event handlers
Like inline script tags, inline event handlers are now out too.
If you’ve written any JS after 2008, you’ve probably used an unobtrusive style of attaching event handlers. But you may still have some inline handlers lurking around your codebase.
<a href="" onclick="handleClick();"></a>
<a href="javascript:handleClick();"></a>
Until Rails 3, Rails itself generated inline handlers for certain link_to
and form_tag
options.
<%= link_to "Delete", "/", :confirm => "Are you sure?" %>
would output
<a href="/" onclick="return confirm('Are you sure?');">Delete</a>
With Rails 3, it now emits a declarative data attribute.
<a href="/" data-confirm="Are you sure?">Delete</a>
You’ll need to be using a UJS driver like jquery-ujs
or rails-behaviors
for these data attributes to have any effect.
Eval
The use of eval()
is also disabled unless unsafe-eval
is set.
Though you may not be using eval()
directly in your app code, if you are using any sort of client side templating library, it might be. Typically string templates are parsed and compiled into JS functions which are evaled on the client side for better performance. Take @jeresig‘s classic micro-templating script for an example. A better approach would be precompiling these templates on the server side using a library like sstephenson/ruby-ejs.
Another gotcha is returning JavaScript from the server side via RJS or a “.js.erb” template. These would be actions using format.js
in a respond_to
block. Both jQuery and Prototype need to use eval()
to run this code from the XHR response. It’s unfortunate that this doesn’t work, since your own server is white listed in the script-src
directive. Browsers would need native support for evaluating text/javascript
bodies in order to enforce the CSP policy correctly.
Inline CSS
Unless unsafe-inline
is set on style-src
, all inline style attributes are blocked.
The most common use case is to hide an element on load.
<div class="tab"></div>
<div class="tab" style="display:none"></div>
<div class="tab" style="display:none"></div>
A better approach here would be using a CSS state class.
<div class="tab selected"></div>
<div class="tab"></div>
<div class="tab"></div>
tab { display: none }
tab.selected { display: block }
Though, there are caveats to actually using this feature. Libraries that do any sort of feature detection like jQuery or Modernizr typically generate and inject custom css into the page which sets off CSP alarms. So for now, most applications will probably need to just disable this feature.
Shortcomings
Bookmarklets
As made clear by the CSP spec, browser bookmarklets shouldn’t be affected by CSP.
Enforcing a CSP policy should not interfere with the operation of user-supplied scripts such as third-party user-agent add-ons and JavaScript bookmarklets.
http://www.w3.org/TR/CSP/#processing-model
Whenever the user agent would execute script contained in a javascript URI, instead the user agent must not execute the script. (The user agent should execute script contained in “bookmarklets” even when enforcing this restriction.)
http://www.w3.org/TR/CSP/#script-src
But, none of the browsers get this correct. All cause CSP violations and prevent the bookmarklet from functioning.
Though its highly discouraged, you can disable CSP in Firefox as a temporary workaround. Open up about:config
and set security.csp.enable
to false
.
Extensions
As with bookmarklets, CSP isn’t supposed to interfere with any extensions either. But in reality, this isn’t always the case. Specifically, in Chrome and Safari, where extensions are built in JS themselves, its typical to make modifications to the current page which may trigger a CSP exception.
The Chrome LastPass extension has some issues with CSP compatibility since it attempts to inject inline <script>
tags into the current document. We’ve contacted the LastPass developers about the issue.
CSSOM limitations
As part of the default CSP restrictions, inline CSS is disabled unless unsafe-inline
is set on the style-src
directive. At this time, only Chrome actually implements this restriction.
You can still dynamically change styles via the CSSOM.
The user agent is also not prevented from applying style from Cascading Style Sheets Object Model (CSSOM).
http://www.w3.org/TR/CSP/#style-src
This is pretty much a requirement if you intend to implement something like custom tooltips on your site which need to be dynamically absolutely positioned.
Though there still seems to be some bugs regarding inline style serialization.
An example of a specific bug is cloning an element with a style attribute.
var el = document.createElement('div');
el.style.display = 'none'
el.cloneNode(true);
> Refused to apply inline style because it violates the following Content Security Policy directive: "style-src http://localhost".
Also, as noted above, libraries that do feature detection like jQuery and Modernizr are going to trigger this exception as they generate and inject custom styles to test if they work. Hopefully, these issues can be resolved in the libraries themselves.
Reporting
The CSP reporting feature is actually a pretty neat idea. If an attacker found a legit XSS escaping bug on your site, victims with CSP enabled would report the violation back the server when they visit the page. This could act as sort of an XSS intrusion detection system.
However, because of the current state of bookmarklet and extension issues, most CSP violations are false positives that flood your reporting backend. Depending on the browser, the report payload can be pretty vague. You’re lucky to get a line number (without any offset) on a minified js file when a script triggers a violation. It’s usually impossible to tell if the error is happening in your JS or some extension inject code. This makes any sort of filtering impossible.
Conclusion
Even with these issues, we are still committing to rolling out CSP. Hopefully a wider CSP adoption helps smooth out these issues in the upcoming CSP 1.1 draft.
Also, special thanks to @mikewest at Google for helping us out.
Written by
Related posts
Students: Start building your skills with the GitHub Foundations certification
The GitHub Foundations Certification exam fee is now waived for all students verified through GitHub Education.
Announcing GitHub Secure Open Source Fund: Help secure the open source ecosystem for everyone
Applications for the new GitHub Secure Open Source Fund are now open! Applications will be reviewed on a rolling basis until they close on January 7 at 11:59 pm PT. Programming and funding will begin in early 2025.
Software is a team sport: Building the future of software development together
Microsoft and GitHub are committed to empowering developers around the world to innovate, collaborate, and create solutions that’ll shape the next generation of technology.