Thursday, December 14, 2006

Session fixation in Rails

If you are not familar with session fixation vulnerabilities, just google it or hit up wikipedia.

By default Rails supports sessions and uses CGI::Session to handle session management. CGI::Session defaults to setting the session id in a cookie, but it has a backup:

"If the client has cookies disabled, the session id must be included as a parameter of all requests sent by the client to the server. The CGI::Session class in conjunction with the CGI class will transparently add the session id as a hidden input field to all forms generated using the CGI.form HTML generation method. No built-in support is provided for other mechanisms, such as URL re-writing."

OK, so there is an avenue to exploit session fixation without having to be able to manipulate cookies (through a client-side vulnerability) in Rails. Hack a little on CGI.form to figure out the parameter name that will make it accept a session id from a form parameter (so the attack would be to modify the form served via an XSS vulnerability or rehost the form in a phish variation). Sessions are on by default in Rails, so we need a to remediate this issue. Here are a couple of options:

1. Disable session support in your application if you can. It may be a little more of a design challenge, but in some cases you can safely avoid having to maintain state between client and server. Edit app/controllers/application.rb and add "session :off" to it. There you go, sessions are disabled.

2. OK, you need sessions. Fine. In an app I wrote, there are a few views that are unauthenticated, a bunch of views that are authenticated, a login mechanism and sessions are used to store error/response messages in the unauthenticated views and to hold the user object in the authenticated views. The risk of session fixation in the unauthenticated views in very minimal, but it does give an attacker a very easy mechanism to obtain a valid session id. Once the user is authenticated, knowing the session id provides access as the user, but that is a different issue (session hijacking) assuming we fix session fixation. We really just need to protect the transition for unauthenticated to authenticated by changing the session id on the transition, which is controlled by the login mechanism. The application is largely RESTful, so a GET to /user/login yields the XHTML login form and a POST to /user/login attempts to authenticate the user with the parameter values. In the user controller, I added "session :new_session => true, :session_expires => (Time::new + (60 * 60 * 24)), :only => :login, :if => Proc::new {|req| req.post? }" The :if parameter provides an anonymous function that checks whether the current request is a POST to be consistent with the REST semantics of user controller actions. The other parameters are self-explanatory. Even if an attacker steers the user to the login view with a know session id, when the user authenticates the session id will change. There you go, authentication yields a different session id.

For the auditors out there, you should be looking for "session :off" or "session :new_session => true" that covers the unauthenticated/authenticated transition in those rails apps growing like weeds in your front yard.

Oh, and I am sure there is a more elegant solution, and with the beauty of rails, I am sure someone will figure out how to package it in a plugin. I also use one-time, time-limited nonces to validate forms were served from the server, but that only hinders the form rehosting (phish) version of the attack (forces the attacker to proxy, a different threat altogether, or periodically re-GET the form, which is generally easy to spot in logs). Dealing with the session fixation issue is still necessary.

3 comments:

Eugueny Kontsevoy said...

Thank you, very useful. I always disable sessions in my .NET projects. With Rails, however, even after I specify session :off for my root app controller, I still see files created in tmp/sessions for every new connection made :-(

Any idea how to fix that? I have very limited options to monitor that directory (hosting issues) and having those files multiply and multiply is not fun :(

Dominique Brezinski said...

The session code is inherited from CGI::session, but I have not looked deep in that code to see if turning off file creation is easy. if you can run cron jobs or scripts periodically, then run "rake tmp:sessions:clear" periodically to remove all those little session files. I will look a little deeper into this issue.

Dominique Brezinski said...

A better solution to those stupid files in tmp/sessions is switch to :mem_cache_store or :memory_store for session storage, though I have not looked at those methods in detail to see what issues they might present.