How to almost protect yourself from the Rails cookie session store
Much has been said about the insecurity of storing Rails sessions in cookies. It leaves you open to:
- Session replay - users can replay their session at any given state and your application would have to accept it.
- No meaningful logout - you have no way to invalidate sessions as they're delivered by the user on every request.
- Readable sessions - if an attacker can grab your secret key base, they can read all of their session data.
- Writable sessions - again with your secret key base, an attacker can write arbitrary values into their session that your application would have to accept. In most Rails applications this probably means they can authenticate as any user they like.
Most discussions of the security drawbacks of the cookie session store stop there. This doesn't come close to the biggest risk cookie-based sessions (the way Rails implements them by default) pose for your application.
The alpha-dragon mack-daddy holy grail of security vulnerabilities is one that allows an attacker to perform remote code execution. If an attacker can grab your secret key base, they can do just that. They can run arbitrary code in your Rails application process at will. That includes shelling out.
If you use the cookie session store in Rails then your value for
secret_key_base is as good as an ssh private key for box your application runs
I've failed to convey the gravity of this vulnerability in conference talks, presentations given to development teams, blog posts, tweets and emails. The only way I've convinced developers to take this one seriously is to make them implement the exploit themselves in the Rails security workshop I run.
But I won't ever lose my secret key base!
There are at least three ways you could lose your secret key base to an attacker that I can think of:
- If you "open source" your web application and publish the secret key base on GitHub.
- (If you realize your mistake and then commit a delete without changing it in production, still counts!)
- If you implement a broken file upload/download mechanism that allows users to download arbitrary files.
- If you package your Rails apps as appliances and use the same secret key base for all customer instances.
Experienced security researchers can probably think of plenty more.
The sensible choice: a server-side session store
Putting your sessions in a server-side store (while only giving a token consisting of random data to the user as a key into it) mitigates all of the security problems above. Pick Redis. Redis is so useful that you probably have it lying around doing something for you already. Stick your sessions and redis and avoid all of this drama.
But you're not going to do that. Almost no one I advise to put their sessions on the server does it. They grumble about a performance overhead, limits on RAM or not being able to scale horizontally. I'm not convinced by these objections. You might be, so let's move swiftly on.
JSON cookie serialization
Finally some good news! It turns out that as of 4.1.0, in newly generated apps
cookie-based sessions are serialized using
JSON instead of
yet, it looks like you can't use the
JSON serializer for arbitrary objects,
only "primitives" i.e. strings, numbers, booleans, arrays and hashes.
This doesn't mitigate the other security issues above, but it does save you from the remote code execution vulnerability.
You don't get this simply by upgrading.
Try generating a new Rails application at a version after 4.1.0. You'll notice
an initializer named
cookies_serializer.rb. It's contents will be something
Rails.application.config.action_dispatch.cookies_serializer = :json
Adding an initializer with that config value setting will make Rails use
to serialize your session data instead of the default
This will invalidate all existing sessions. If you don't care about this, then you can skip the rest of this post. If you'd rather your users aren't all booted out of the application when you deploy this change, Rails has you covered.
:json, set the value to
:hybrid. This will cause Rails to accept
sessions serialized with
Marshal and exchange them for sessions serialized
After you're confident that all your users sessions have been converted to
JSON, you can roll out another release that flips the config value to
Note: If you're storing complex Ruby objects in the session and need them to be
Marshal, you won't be able to use the
You should still seriously consider keeping your sessions server side, but
JSON session serialization at least prevents you from being owned