What's New in Edge Rails: Better Cross-Site Request Forging Prevention

Posted by ryan
at 8:50 AM on Monday, September 24, 2007

Hot on the heels of the in-depth look at Rails security options comes the addition of the CsrfKiller plugin into rails core.

The CsrfKiller plugin adds a unique session token field to all forms that is checked on every non-GET request. This ensures that the request received is in fact coming from the session of the authorized user (check out wikipedia’s CSRF article if you need more details on the technique).

All you need to do to enable this protection is to add a protect_from_forgery statement in your controller that takes the familiar :except or :only option along with salt to use when generating the unique token:

And now this is the default functionality. No need to specify anything if you want the default behavior. You can still override as in the following examples, however.

1
2
3
4
5
class PostsController < ApplicationController

  protect_from_forgery :secret => '2kdjnaLI8', :only => [:update, :delete, :create]
  ...
end

If you’re already using edge Rails’ default cookie session store then you don’t have to specify the :secret key.


protect_from_forgery :only => [:update, :delete, :create]

If you’re not on a cookie session store you can also change the digest method used to generate the unique token (the default is ‘SHA1’).


  protect_from_forgery :secret => '2kaienna9ea90djnaLI8', :digest => 'MD5'

If a request comes in that doesn’t match the request forgery protection token for the current session then an ActionController::InvalidAuthenticityToken exception will be thrown. Perhaps a good place to try out the new exception handling syntax ?

Caveats: The request forgery protection only kicks in in the following scenarios:
  • Non-GET requests, so make sure the only requests that modify state are your PUT/POST/DELETE requests.
  • On html and ajax requests. Override verifiable_request_format? if you want to expand that.

And what to do if you want to disable CSRF protection? Just add this to whatever controllers you don’t want to be affected:


skip_before_filter :verify_authenticity_token

And if you want to disable this site-wide, just add this to application.rb:


self.allow_forgery_protection = false

tags: ruby, rubyonrails

Comments

Leave a response

  1. Robert EvansSeptember 26, 2007 @ 08:40 PM

    One interesting bug with this is if you turn on active_record_store, you’ll be greeted with this error:

    undefined method `generate_digest’ for #<cgi::session::activerecordstore:0x303f7fc>

    A quick way around it is to add the secret to protect_from_forgery, instead of relying on it in your environment.rb file.

    I’m looking at this a bit closer to see what the issue is.

  2. Stephen TousetSeptember 27, 2007 @ 08:39 PM

    This seems like it would kill REST clients trying to make non-GET requests to resources, no?

  3. rickSeptember 28, 2007 @ 12:19 PM

    No, it only protects based on the format (see #verifiable_request_format? http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/request_forgery_protection.rb#L88). XML/JSON api requests would typically use some other session-less form of authentication, such as http basic or an api token.

    Robert: thanks for the heads up. For those playing at home, there’s a rails ticket for it: http://dev.rubyonrails.org/ticket/9670 and it’ll be fixed for the 2.0 preview…

  4. rickSeptember 28, 2007 @ 12:21 PM

    ok try this for the trac link. How sad is it the author of this blog tool can’t post comments correctly with it?

  5. Robert EvansSeptember 28, 2007 @ 06:10 PM

    One other thing I noticed, when doing an ajax request (link_to_remote) you need to explicitly state what type of request. (:method => :get) I added this the trac.

  6. Robert EvansSeptember 29, 2007 @ 02:31 PM

    As Rick pointed out in the trac ticket, I was mistaken by the above. I wasn’t even thinking that link_to_remote was a POST by default…doh. Thanks for the clarification Rick.

  7. Cyril DavidOctober 04, 2007 @ 10:16 AM

    One thing I noticed is that tests for because the mock for cookie cgi doesn’t have a dbman method (unlike the cookie session store)

    How do you guys work around this? Just adding a stub method for the cookie cgi? Suggestions would be nice

    (I worked around this by using a diff session management store during tests)

  8. PhilipOctober 25, 2007 @ 11:23 AM

    Good post – I was just looking for the info on how to skip the forgery protection for one of my controllers. However, why isn’t this info in your rails 2 pdf, which i incidentally have just bought!

  9. RyanOctober 27, 2007 @ 08:38 AM

    Hey Philip,

    This isn’t in the peepcode because I wasn’t sure how prevalent its usage would be and we had to draw the line somewhere. However, a few more people making a request such as yours and I’ll be sure to add it!

    Thanks for the feedback.

  10. Akhil BansalOctober 29, 2007 @ 04:42 AM

    This is really a good point to cover in my presentation today ;-)

    Thanks Ryan,

  11. stwfNovember 08, 2007 @ 10:57 AM

    Hi, This seems to be why my ajax requests (using JQuery’s load and url_for) fail when I’m sending additional parameters to the load function. This changes it from a Get to a POST.

    Could someone post a way to manually add the authenticate token for this newbie?

    TIA steve

  12. JamalNovember 09, 2007 @ 11:54 AM

    I actually added this line

    self.allow_forgery_protection = false

    to my application controller, but this did NOT change anything, I still get the usual error

    InvalidAuthenticityToken

  13. JamalNovember 09, 2007 @ 11:55 AM

    I actually added this line

    self.allow_forgery_protection = false

    to my application controller, but this did NOT change anything, I still get the usual error

    InvalidAuthenticityToken

  14. Kamal FarizNovember 28, 2007 @ 08:18 PM

    I discovered that when running a Facebook application on Rails, you should explicitly set your own salt rather than relying on the Cookie Session Store to generate it for you. I haven’t looked deeply into how the cookie generates this salt, but it’s clear that it changes in between requests when round tripping in Facebook, thus making Rails mark it as invalid.

    My write up on the issue is here: http://blog.ror.com.my/2007/11/28/facebook-rails-gotchas-csrf-protection-and-cookie-session-store/

    Hope this will help someone else when Googling for the same problem.

  15. Luke FranclNovember 29, 2007 @ 07:33 PM

    I was having the same problem as Kamal, except not with Facebook. If validation failed, I would get an ActionController::InvalidAuthenticityToken error when re-submitting the form.

    However, the problem wasn’t with Rails, it was with an older version of Restful Authentication, as detailed in this ticket.

    To fix it, I just had to remove the reset_session call from UsersController#create.