Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10108 (closed defect: invalid)

Opened 8 months ago

Last modified 8 months ago

protect_from_forgery reveals underlying inconsistency with Session IDs

Reported by: igotimac Assigned to: technoweenie
Priority: high Milestone: 2.0
Component: ActionPack Version: edge
Severity: major Keywords: forgery, protect_from_forgery, session, session_id
Cc:

Description

Steps to reproduce:

  1. Start with any rails app which some kind of form and uses protect_from_forgery
  2. Clear your cookies
  3. In your browser hit the form with a single GET
  4. POST the form

(This works only when the single GET is cookie-less and when the POST happens from the form returned by that GET)

The result:

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb:73:in `verify_authenticity_token'

...

It appears to me, that this is related to the fact that Session ID is different between that first GET request when I didn't have any cookies and that second POST request:

Processing LabsController#new (for 127.0.0.1 at 2007-11-08 11:37:41) [GET]

Session ID: 9c79096681ef943799a2d663f2511891 Parameters: {"action"=>"new", "controller"=>"labs"}

...

Processing LabsController#create (for 127.0.0.1 at 2007-11-08 11:37:44) [POST]

Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--fdeb33a5c5e160863b4921ba4c06dad38348abb6 Parameters: {"lab"=>{"name"=>""}, "authenticity_token"=>"808cb7f2bf7b1afe00d67de669e8663eb83586e5", "action"=>"create", "controller"=>"labs"}

...

I'm assuming this authenticity check failed because my Session ID changed, so why did my Session ID change?

Change History

11/08/07 18:36:50 changed by igotimac

my session ID also gets messed up when I Post the form, get back validation errors, and then post again:

Processing DevicesController#create (for 127.0.0.1 at 2007-11-08 13:31:43) [POST]

Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--fdeb33a5c5e160863b4921ba4c06dad38348abb6 Parameters: {XXXXXX "authenticity_token"=>"13dc1f99f148ca32c3db12d39a5db57ae1cfeb91", XXXX}

...

Processing DevicesController#create (for 127.0.0.1 at 2007-11-08 13:31:49) [POST]

Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsGOgplcnJvciIZVGhlcmUgd2FzIGEgcHJvYmxlbSEGOgpAdXNlZHsG%0AOwZU--517ca1aae7e156e78ebbaf8a2b297f34e0c346f5 Parameters: {XXXXX "authenticity_token"=>"13dc1f99f148ca32c3db12d39a5db57ae1cfeb91", XXXX}

...

and I get: ActionController::InvalidAuthenticityToken

I'm beginning to think there must be a problem in the way I've configured sessions, otherwise somebody else would have already filled this bug, right? But, I'm just using the defaults from a new rails app!

11/08/07 19:41:03 changed by JasonRoelofs

protect_from_forgery seems to be just plain broken right now.

From restful_authentication:

  def create
    cookies.delete :auth_token
    reset_session
    @account = Account.new(params[:account])
    @account.save!
    self.current_account = @account
    redirect_back_or_default('/')
    flash[:notice] = "Thanks for signing up!"
  rescue ActiveRecord::RecordInvalid
    flash[:error] = "Please fix the following errors"
    render :action => 'new'
  end

When this fails, we simply render new again, which is just showing a simple form. When I POST again (URL is now /accounts instead of /accounts/new), I get InvalidAuthenticityToken.

I checked the token values in the HTML many times, and they're always the same between both renderings of the form. So I guess this feeds off of the page's path, which can't be right.

11/08/07 19:44:55 changed by JasonRoelofs

Sorry for double post, but was just chatting with co-worker and he brought up the though that the authenticity_token value isn't getting cleared from params[] after the authentication is taken care of.

Possible source of the problem? I don't know much about the system, but this makes sense.

11/13/07 10:47:03 changed by marclove

  • priority changed from normal to high.
  • milestone changed from 2.x to 2.0.

+1

I'm experiencing the same issue as Jason and igotimac. You're right Jason, the problem comes from the fact that the authenticity_token is not updated when render is called to re-show the form that failed validation. If you refresh the page before submitting the form again, you won't get any error.

I don't think this has anything to do with inconsistency with Session IDs but I'm not familiar enough with the implementation to know so I'll leave the title as is.

11/18/07 22:01:47 changed by david

  • owner changed from core to technoweenie@gmail.com.

11/19/07 00:03:27 changed by technoweenie

  • owner changed from technoweenie@gmail.com to technoweenie.
  • status changed from new to assigned.

I'd like to see some kind of failing test. I tried the steps you suggested, but it works fine for me. It only fails if I render the form, clear the cookies, and then post.

11/19/07 02:16:24 changed by marclove

I'll work on creating a failing test case, but in the meantime here's the exact steps I just used to replicate the error:

ruby ~/projects/edge_rails/railties/bin/rails pff_test
cd pff_test
ln -s ~/projects/edge_rails/ vendor/rails
script/plugin install restful_authentication
script/generate authenticated user sessions
rake db:create
rake db:migrate
script/server

I go to "users/new" and complete login and email but leave the password fields blank.

Submit

The new user form is redisplayed with flash error messages. I go ahead and complete the password fields.

Submit

ActionController::InvalidAuthenticityToken in UsersController#create

11/19/07 04:38:24 changed by technoweenie

  • status changed from assigned to closed.
  • resolution set to invalid.

Well, that's because restful auth clears the session. Just remove that 'reset_session' line in your create action. I'm closing this since it's related to some generated code, and not the framework itself. I'll just remove it from the plugin since session fixation attacks won't be possible in 1.2.6 and rails 2.0.

I wrote the request forgery stuff so that it should be easy to swap out the routine that builds and verifies the tokens. But, you'd need some other kind of unique info for a user. SessionID seems like the safest bet to me. But, this is open to debate. It was written as a plugin a long time ago for this purpose, but no one spoke up :)

Please debate on the rails-core list, or re-open the ticket if you have a patch. Thanks.