Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting Rails 4 Session cookie tampering

Background

I'm an experienced web developer (mostly with Python and CherryPy) who has implemented secure session management from scratch before, and is now learning Rails. I'm investigating the behavior of Rails sessions as exposed by the session object that is available in the ActionController instance and view contexts.

Question/Problem

I have read that the default implementation of sessions in Rails 4 uses an encrypted and tamper-proof cookie. Cool, I guess that means I can use it to hold a user ID for user sessions without worrying about session forging (tamper-proof) or anyone being able to find out what their ID is (encrypted). I wanted to test this and see what rails would do if the session cookie was altered.

So, I went and altered the content of the session cookie attribute using a browser add-on, and when I reload the page with the new cookie value, Rails just happily gives me different new values for session_id and _csrf_token.

What happened to session cookie integrity!?

Shouldn't rails detect (via HMAC signature) that the cookie was altered and then tell me about it somehow?

I'm terrified that I'm missing something obscenely obvious, but I've been having no luck searching for an answer on the web, and the source code isn't giving it up easily either (I'm new to ruby). Thanks in advance.

The Experiment

I created a new app and generated a controller with an index action:

$ rails new my_app
$ cd my_app; rails g controller home index

Then I added these two lines to the app/views/layouts/application.html.erb file:

<%= session.keys %><br/>
<%= session.values %>

I started up the dev server and navigated my browser to "localhost:3000/home/index". As expected, the page has the following lines at the bottom:

["session_id", "_csrf_token"]
["8c1558cabe6c86cfb37d6191f2e03bf8", "S8i8/++8t6v8W8RMeyvnNu3Pjvj+KkMo2UEcm1oVVZg="]

Reloading the page gives me the same values, although the app sets a new value of the _my_app_session cookie attribute every time. That seems weird to me, but I'm getting the same session hash values, so I guess it's cool.

Then, I used a cookie editing add-on for Chrome to alter the value of the _my_app_session cookie attribute (replacing the first character of the attribute value). Reloading the page shows completely different values without anything happening. WAT?

like image 511
mhess Avatar asked Sep 11 '13 21:09

mhess


1 Answers

I can't claim a really thorough understanding of the code here. But I can tell you this much:

I followed your steps exactly (using Ruby 2.0.0-p247 & Rails 4.0), with one exception -- I also added the 'byebug' gem to my Gemfile and inserted a debugging breakpoint in the HomeController#index action.

From the byebug console, at that breakpoint, I could see the unedited session cookie via:

(byebug) cookies["_my_app_session"]
"cmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"

And I could see the actual encrypted values with

(byebug) cookies.encrypted["_my_app_session"]
{"session_id"=>"13a95fb545a1e3a2d4e9b4c22debc260", "_csrf_token"=>"FXb8pZgmoK0ui0qCW8W75t3sN2KLRpkiFBmLbHSfnhc="}

Now, I edit the cookie by changing the first letter to "A" and refresh the page:

(byebug) cookies["_my_app_session"]
"AmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"
(byebug) cookies.encrypted["_my_app_session"]
nil

So the session is nil at this point in the request:

(byebug) session
#<ActionDispatch::Request::Session:0x7ff41ace4bc0 not yet loaded>

I can force loading the session with

(byebug) session.send(:load!)

and when I do, I see that the resulting session id is

"f6be13fd646962de676985ec9bb4a8d3"

and sure enough, when I let the request finish, that's what I see in the view:

["session_id", "_csrf_token"] ["f6be13fd646962de676985ec9bb4a8d3", "qJ/aHzovZYpbrelGpRFec/cNlJyWjonXDoOMlDHbWzg="]

I also have a new cookie value now, unrelated to the one I edited.

So from this I think we can conclude is that what's happening is that since the cookie signature could not be verified, the session was nullified and regenerated. I now have a new session, with a different csrf_token.

The relevant code appears at actionpack/lib/action_dispatch/middleware/cookies.rb:460-464, in the EncryptedCookieJar class:

def decrypt_and_verify(encrypted_message)
  @encryptor.decrypt_and_verify(encrypted_message)
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
  nil
end

Rather than decrypting a message with an invalid signature, we just treat it as nil. So the unverifiable cookie that stores the session id and csrf token is not used to load the session, and anything that depends on the values in the cookie will fail.

So why didn't we get an error rather than just a new session? That's because we didn't try anything that depends on the encrypted values. In particular, although we have

protect_from_forgery with: :exception

(as opposed to :null_session) in ApplicationController, Rails does not verify the csrf token on GET or HEAD requests -- it relies on the developer to implement these actions according to spec, so that they're non-destructive. If you tried the same thing on a POST request, you'd get an ActionController::InvalidAuthenticityToken error (as you can easily verify for yourself).

like image 183
gregates Avatar answered Oct 13 '22 15:10

gregates