Rails 2.0 CookieStore now secure
Ruby on Rails 2.0′s CookieStore, which was considered insecure before, should now be secure. My security patch has been accepted, so once again the world has turned into a better place to live in.
Ruby on Rails 2.0′s CookieStore, which was considered insecure before, should now be secure. My security patch has been accepted, so once again the world has turned into a better place to live in.
In the initial version of my blog post Rails 2.0, cookie session store and security, I concluded that, if given a sufficient complex secret, forging the session data is computationally infeasible. Jamie Flournoy’s comment (see the comments section), as well as this page, turned the tide.
Jamie Flournoy has pointed out that there are flaws in Rails’s secret key generator. The generator is based on the session ID generator in Ruby’s CGI::Session module. Upon investigation, it turns out that CGI::Session’s session ID generator uses a method similar to the one used in Netscape’s SSL implementation in 1995 – which was cracked! So this seems to make it computationally feasible for a skilled attacker to brute force the secret.
I now believe that CookieStore is insecure by default, but the only reason why that is so is because the secret key generator in Rails is flawed. This is something that can be fixed. I believe that, once the secret key generator has been fixed, CookieStore will be secure enough by default. The secret key generator should be fixed ASAP.
I’m now working on a patch that uses /dev/urandom, the Win32 API and/or OpenSSL to create the key, depending on whatever best method is available on the platform.
I have written a script which tries its best to generate a secure random key. It will use /dev/urandom, the Win32 API’s crypto functions, OpenSSL, and plain old rand()+digest, whatever best method is available on the current platform. Download SecretKeyGenerator2.rb
Code from this script can be copied & pasted into Rails’s app_generator.rb.
UPDATE: I updated the script with SecureRandom support.
UPDATE 2: I’ve submitted a patch for edge Rails.
UPDATE 3: The patch has been accepted.
(Note: outdated text is marked in red. See “Update November 28″ at the bottom of this post for details.)
Those who follow Ruby on Rails development are probably aware of the fact that Rails 2.0 will use a new session store by default, namely CookieStore.
Rails 1.2 defaults to PStore, which works as follows:
There are other session stores available for Rails 1.2 as well, such as MemCache store, MemoryStore, ActiveRecord store and SQLSessionStore. These stores work with session IDs as well, but store the session data in memcache, memory and database, respectively.
The reason why the Rails core developers introduced CookieStore is because PStore is known to cause performance problems and deadlocks. Furthermore, PStore requires the system administrator to clean up outdated session files once in a while. Michael Koziarski said: “So almost every prodution rails application has already switched to active_record_store or some other alternative and their apps will continue to use this alternative store.”
CookieStore on the other hand has the following benefits compared to PStore:
CookieStore is implemented by serializing session data into a cookie. Here’s an example of such a cookie:
BAh7BzoMdXNlcl9pZGkKIgpmbGFzaElDOidBY3Rpb25Db250cm9sbGVyOjpG%250 AbGFzaDo6Rmxhc2hIYXNoewAGOgpAdXNlZHsA–be9c1e802c6cf126c722c680 02ccbd5684a96dd9
The part before the “–” is the Base64 encoding of the serialized data. The part after is an HMAC. This HMAC is generated based on the serialized data, and a server-side secret.
This has huge security implications. If an attacker is able to determine the secret, then he can forge session data in any way he likes. Consider the following scenario’s:
The current session stores (PStore, ActiveRecord store, etc.) have none of these problems because they all work with session IDs. It should not be surprising that the introduction of CookieStore has generated a lot of controversy. My first reaction, too, was “WTF? Are they out of their minds?”.
To this date, the controversy remains. I think that there’s a lot of misinformation and fear floating around, so I hope to educate people with this post.
The Rails core team seems to think CookieStore is a good idea. I decided to do more research, and this is what I found:
Points 1 and 2 are in my opinion very valid, so the only concerns left are point 3, and whether session replay attacks are possible.
According to Heiko Webers and Corey Benninger, the secret can be locally brute forced (which is certainly true), but they make it sound like it’s an easy thing to do. Benniger wrote a Ruby script for brute forcing the secret. On my machine, the example cookie provided by Webers is cracked within 1 second (the secret is “foo”). But this is a very insecure password anyway.
The blog posts written by Webers and Benninger are a bit outdated. According to them, Rails only checks whether the secret is blank. This is not true: in the latest implementation, Rails forces the secret to be at least 30 characters long. Furthermore, the Rails app generator creates a long, unique secret: an MD5 in hexadecimal format which is generated as follows: (UPDATE: the code below is outdated)
So by default, Rails apps will have a secret that's 128/8*2=32 characters long, containing both alphabetical and numerical characters. A brute forcer will have to try 16^32=3.403*10^38 possible secrets in the worst case, and perhaps 16^32/2=1.701*10^38 possible secrets in the average case.
I'm not sure whether there are faster ways to crack the secret. I have heard that there's an algorithm called SmartForce, which can brute force passwords much faster. But according to the description page of SmartForce, it doesn't work with passwords that contain digits, so the default secret generated by Rails is safe from that algorithm.
I wrote my own brute forcing program just to see how fast my machine can brute force passwords. This program is written in C for raw number crunching speed. It is multithreaded, so it can take advantage of your dual/quad core machines. Use it as follows:
tar xzvf crackrailscookie.tar.gz cd CrackRailsCookie make ruby GenerateSessionCookie.rb john -i --stdout | ./CrackRailsCookie
It requires a POSIX environment (e.g. Linux, MacOS X, Cygwin) and that John the Ripper is installed.
GenerateSessionCookie.rb generates a Rails session cookie data and digest file, based on the session data and secret defined in its source code. CrackRailsCookie will try out passwords fed by John the Ripper, until it has found the correct one. You can specify the number of threads used by CrackRailsCookie, by specifying it as the first argument, i.e.
john -i --stdout | ./CrackRailsCookie 5
will spawn 5 threads.
My machine, an Intel Core 2 Duo T5300, can try 285840.1 passwords per second with 2 threads. To crack the default secret specified in GenerateSessionCookie.rb, "449fe2e7daee471bffae2fd8dc02313d", my machine will have to run for
16^32/2 / 285840.1 = 5.952*10^32 seconds = 1.886*10^25 years
Ouch, I don't think the universe will even last that long if we keep observing it.
However, Jamie Flournoy has pointed out that there are flaws in Rails's secret key generator. See his comment below. The generator is based on the session ID generator in Ruby's CGI::Session module. Upon investigation, it turns out that CGI::Session's session ID generator uses a method similar to the one used in Netscape's SSL implementation in 1995 - which was cracked! So this seems to make it computationally feasible for a skilled attacker to brute force the secret.
I don't think session replay attacks are something that we have to worry about. At least, when compared to the other session stores out there.
An attacker can make himself logged in indefinitely by never expiring the session cookie. But the current session stores have this problem as well. The only way to fight this is to check the session's expiration date in the server. The session will have to contain an 'expires_at' or 'created_on' field of some sort. If it is computationally infeasible to forge session data (i.e. if it is infeasible to crack the secret), then we can be sure that the 'expired_at'/'created_on' field can be trusted. If so, then session expiration is not a problem.
Another way an attacker can do damage, is by sniffing the session cookie of a different user, and then using that to forge his identity. However, this is the same as sniffing that user's session ID, or sniffing the password that that user sends over his network. So at this aspect, CookieStore is not any less secure than all the other session stores. If people can sniff your network data then you're screwed anyway. Better use SSL for the really important stuff.
If the application stores important, non-volatile-natured state information in the session, like 'bank_account_balance', then yes, CookieStore is vulnerable to a session replay attack. However:
In the initial version of this blog post, I concluded that, if given a sufficient complex secret, forging the session data is computationally infeasible. Jamie Flournoy's comment (see below), as well as this page, turned the tide. I now believe that CookieStore is insecure by default, but the only reason why that is so is because the secret key generator in Rails is flawed. This is something that can be fixed. I believe that, once the secret key generator has been fixed, CookieStore will be secure by default.
Rails forces the secret to be at least 30 characters long, so I don't think we have to worry about uninformed developers inputting an insecure password. And although the client can see the content of the session, putting sensitive information in the session is a bad idea anyway. The inability to forge session data prevents makes session replay attack problems not any more severe than the other session stores.
So I agree with the Rails core team and welcome CookieStore as the default session store, provided that they fix their secret key generator.
Update November 25: Added information on session replay attacks.
Update November 26: I made a mistake in calculating the size of an MD5-based secret. This is fixed now.
Update #2 November 26: Updated this post with information on flaws in Rails's secret key generator.
Update November 28: The key generation security problem has been fixed.