CSRF Protection Bypass in Ruby on Rails

Posted by michael February 08, 2011 @ 10:53 PM

There is a vulnerability in Ruby on Rails which could allow an attacker to circumvent the CSRF protection provided. This vulnerability has been assigned the CVE Identifier CVE-2011-0447.

  • Versions Affected: 2.1.0 and above
  • Not affected: Applications which don’t use the built in CSRF protection.
  • Fixed Versions: 3.0.4, 2.3.11

Impact

Certain combinations of browser plugins and HTTP redirects can be used to trick the user’s browser into making cross-domain requests which include arbitrary HTTP headers specified by the attacker. An attacker can utilise this to spoof ajax and API requests and bypass the built in CSRF protection and successfully attack an application. All users running an affected release should upgrade or apply the patches immediately.

Releases

The 3.0.4 and 2.3.11 releases are available at the normal locations.

Upgrade Process

There are two major changes in this fix, the behaviour when CSRF protection fails has changed and the token will now be required for all non-GET requests.

After applying this patch failed CSRF requests will no longer generate HTTP 500 errors, instead the session will be reset. Users can override this behaviour by overriding handle_unverified_request in their own controllers.

Users must still take care that users cannot be auto logged in via non-session data. For example, an application using filters to implement ‘remember me’ functionality must either remove those cookies in their handle_unverified_request handlers or ensure that the remember me code is only executed on GET requests. A custom handler which removes the remember_me cookie would look like:

def handle_unverified_request
   super # call the default behaviour which resets the session
   cookies.delete(:remember_me) # remove the auto login cookie so the fraudulent request is rejected.
end

There are two steps to ensuring that your application sends the CSRF Token with every ajax request. Providing the token in a meta tag, then ensuring your javascript reads those values and provides them with each request. The first step involves you including the csrf_meta_tag helper somewhere in your application’s layout. Rails 3 applications likely already include this helper, however it has now been backported to the 2.3.x series. An example of its use would be something like this in application.html.erb:

<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>

In addition to altering the templates, an application’s javascript must be changed to send the token with Ajax requests. Rails 3 applications can just update their rails.js file using rake rails:update, 2.x applications which don’t use the built-in ajax view helpers will need to add a framework-specific snippet to their application.js. Examples of those snippets are available:

Workarounds

There are no feasible workarounds for this vulnerability.

Patches

To aid users who aren’t able to upgrade immediately we have provided patches for the two supported release series. They are in git-am format, 3-0-csrf.patch includes two changesets, the others consist of a single changeset.

Given the severity of the problem we are also providing backported fixes to the 2.2 and 2.1 series. There will be no gem releases for these versions but the stable branches in git will be updated.

Please note that only the 2.3.x and 3.0.x series are supported at present. Users of earlier unsupported releases are advised to upgrade as soon as possible as we cannot guarantee continued security fixes indefinitely.

Credits

Thanks to Felix Gröbert of the Google Security Team for reporting the vulnerability to us and working with us to ensure that the fix didn’t introduce any new issues. Thanks also to the Shopify development team for their assistance in verifying the fix and the upgrade process. The original vulnerability has been reported to vendors by kuza55

16 comments

Comments

  1. Brad on 09 Feb 00:48:

    Updating the rails javascript files is ‘rake rails:update’ not ‘rake rails:upgrade’

    Thanks for all the work.

  2. anon on 09 Feb 04:31:

    The rails.js update (rake rails:update for Rails 3) is Prototype-specific, is it not? Do you have a jQuery version of the necessary patch?

  3. Sebastian Gräßl on 09 Feb 08:33:

    @anon: take a look at https://github.com/rails/jquery-ujs As you said it is Prototype specific. It updates only the js files to the ones shipped with that rails update.

  4. Jon Atack on 09 Feb 11:01:

    Upgraded and all working so far. Thanks to all at the Rails team for the continued great work.

  5. Jonathan del Strother on 09 Feb 11:59:

    The jquery snippet doesn’t work on jquery 1.5 as far as I can tell. The ajaxSend event fires after the xhr state is set to 1, so setRequestHeader is ignored. I’ve been trying to figure out a sensible way of patching it, but might have to resort to just reordering jquery’s ajaxSend event to occur before the “state = jXHR.readyState = 1;” line…

  6. Jonathan del Strother on 09 Feb 12:14:

    Turns out that the setRequestHeader problem has already been fixed – https://github.com/jquery/jquery/commit/a2dbdc1f5438a857c2a9898bd36e4b2de685742e. Hopefully we’ll see that in jquery 1.5.1.

  7. Thomas Krampl on 09 Feb 15:51:

    This works for me with jQuery 1.5 https://gist.github.com/818689

  8. Jon on 09 Feb 20:47:

    Also for jQuery <= 1.5, you can use xhr.setRequestHeader to set the token in ajaxSetup -> beforeSend

  9. Rasmus on 10 Feb 00:08:

    Thomas, thanks! Your fix works great.

    Might be of help to others: Remember to output csrf_meta_tag BEFORE including jquery and the jquery fix.

  10. Mislav on 10 Feb 13:03:

    I’ve just patched official rails.js with code that works in versions of jQuery including a workaround for 1.5: https://github.com/rails/jquery-ujs/blob/443de05/src/rails.js#L9-27

  11. Kyriacos on 10 Feb 20:33:

    my bad. Got it working missed out something before. Thanks

  12. kaczor1984 on 11 Feb 08:05:

    When updating rails 2.3.* check your rack version. Rack 1.0.0 threw undefined method `set_cookie_header!’ for Rack::Utils:Module after upgrading to 2.3.11

  13. jamieorc on 14 Feb 20:43:

    Could you set this in ajaxSetup() as well, or are they executed differently?

    jQuery.ajaxSetup({ ‘beforeSend’: function(xhr) { var token = $(“meta[name=’csrf-token’]”).attr(“content”); xhr.setRequestHeader(“X-CSRF-Token”, token); } })

  14. Mike Boone on 16 Feb 15:44:

    I found this a helpful writeup:

    http://jasoncodes.com/posts/rails-csrf-vulnerability

    This is worth quoting…it’s in reference to doing your own cookie deletion in handle_unverified_request: “Please do not blindly upgrade to 2.3.11/3.0.4 without carefully testing your authentication system with POST requests lacking an authentication token. Without adding the above code we would effectively lose CSRF protection as the app will still see the authentication and process the request where there’s an invalid or missing authenticity token.”

  15. Mislav on 24 Feb 19:07:

    Official rails.js for jQuery patched again, this time fixing an issue with IE: https://github.com/rails/jquery-ujs/blob/a284dd7/src/rails.js#L9-15

    Warning: don’t use the `ajaxSetup` + `beforeSend` snipped provided by others (@jamieorc and the article in the above post). This in an incomplete solution.

  16. jon on 25 Feb 18:18:

    I’ve never applied a patch to a gem. Can anyone give some advice?