One of the keys of Rails’ success is that provides a full stack where you can start from. This awesome full stack, as any other piece of engineering, was built upon design decisions. You take them for granted, and when you code on Rails, you assume that decisions others have taken for themselves are good for you too.
Precisely one of Rails’ focuses is about letting you start easily a project just
rails new command away. Now you have a nice directory structure, a database
connection, CSS/JS preprocessors… a cool new toy with batteries included. You delegate
to Rails the low level details, whilst you concentrate on the high level stuff that really matters to your project.
But, let’s think for a second. Which of those things are you going to ditch in the near future whose solely purpose was to let you start quickly?
Well, one of those is certainly the database connection. Nobody uses sqlite in high concurrency web apps. No big deal, you change the database driver among a couple of other retouches and you are ready to go. Or not?
Enter the sessions
One of the things that came for free on
rails new, is a default session store,
without any need to configure. And the nasty surprise is this is one of the things you assumed that they were good for you.
Rails cookie based session storage was introduced as the default in 2007, version 2.0.0. Previously the default (in version 1.2.6) was a file store, but was ditched for performance reasons (it’s faster to avoid a disk access) and scalability reasons (different servers may not share the same filesystem).
Then it seems that a plain cookie is storing your session variables after all. Does it mean that session information can be modified as if we were storing our information in a cookie? No, because sessions have a signature that prevents tampering.
Accessing the session data
Unfortunately, breaking the anti-tamper signature is easy.
I’ve prepared 3 simple projects, using Rails 2, 3 and 4. All 3 have equivalent
functionality, controllers and routes. Uncompress the file
and checkout branch
rails-4 to use one of the projects.
They have a
/users resource done with a basic Rails scaffolding. It has been modified
to allow sending a special parameter to set a session value.
Let’s start the dev server on the rails-2 branch and follow these steps in your browser:
http://localhost:3000. No session data has been set yet.
- Set session data adding the parameter
http://localhost:3000. The header shows the session data you’ve just set.
<p class="caption">Setting a session value in the browser.</p>
Session data is just the value of the cookie named
<p class="caption">Getting the session cookie with the browser.</p>
You can try to get the value of that cookie in any of the other projects:
_three_session in rails-3 and
_four_session in rails-4.
Decode the session data
In Rails 2 & 3, the value of the cookie is a base64 encoded serialized string with an added signature. Session data is thus almost clear text.
However, in Rails 4, the value of the cookie is an encrypted string. But if you have access to the source code of the application you can use the built-in infrastructure to decode the session.
Get the session data in
session_data by opening a Rails console and running the required commands:
<p class="caption">How to decode the session cookie in different Rails versions.</p>
Altering session data
With the session data in our hands, it’s very simple to add our own values…
session_data['option'] = 'boom'
…regenerate the cookie…
<p class="caption">How to regenerate the session cookie in different Rails versions.</p>
…to get the new payload:
And request the page with the new payload:
curl --cookie "_two_session=NEW_PAYLOAD" http://localhost:3000/users
Finally, behold the output. Boom!
Session store alternatives
Rails provides several session store alternatives. All these store the session data far from the user. The only link between the user and the session is the session identifier, a very SHA1-like string. Although this session id can be tampered with using the same techniques we’ve seen before, the chances that you guess a valid session id matching a current user are really low.
<p class="caption">Configuration of several session store alternatives.</p>
In order to decide which session store is right for you, you might apply different criteria regarding your knowledge of the technology it uses, costs of storage, reliability, memory usage and others. But a good rule of thumb is to always have an eye on performance as a criteria to include in your decisions.
To perform some benchmarks you can check out the Rails 4 project, already configured
with different backends in each branch
Start the development server and then run:
…from another terminal. The first benchmark creates a session and reuses it several times. The second one writes to the session each time.
<p class="caption">Benchmarks results.</p>
Cookie-based is the fastest one. SQL-based storages are the slowest, MySQL being 1% slower than SQLite (considering that here we only have have one user). It is highly recommended to run these benchmarks in several environments, as it may render different results depending on hard disk type (SSD or not), memory size and speed, processor, configuration of the backend, load of the backend and even session size and the kind of data being stored in the session.
The bottom line
We’ve demonstrated that cookie-based sessions are not the safest way to preserve the session
data as they are publicly visible in Rails 2 & 3, and easily modifiable to anyone that has the
Other problems with cookies are:
- they can only hold up to 4 kb of serialized data. While it’s a bad practice to store loads of data in the session, this can be a limiting factor in certain circumstances.
- the cookie is always sent by the browser in same-domain requests, slowing requests as the cookie gets heavier. You can minimize this effect by serving your assets from a different domain.
- sessions are invalidated in the client side as a result of the cookie expiring policy. This means that a session cookie will always be cosidered valid for the server, posing a security problem if a cookie falls on undesired hands. Alternative session stores are invalidated both server side and client side.
Session cookies are good enough for a quick project start, or a personal project. But as your project starts to grow you should evaluate and choose an alternative according to your requirements.
Avoiding details will let you go far, but knowing them will let you go further. Try to question your dogmas from time to time, because that will let you know your tools better.