Create a new Rails app, open
/config/initializers/secret_token.rb and you’ll see your app’s
secret_token. As I will show you, if anyone who wishes you harm gets hold of this string then they can execute arbitrary code on your server. Troublingly, the Rails default includes it in your version control, and if you don’t remove it then anyone who gets or is given access to your codebase has complete, complete control over your server. Maybe you added them to your private repo for a code review, or unthinkingly put a side-project production app into a public repo, or someone sneaked a look at your Sublime while you were out. It doesn’t matter - if they have this key then they own you.
secret_tokenis important - session cookies
secret_token is used for verifying the integrity of your app’s session cookies. A session cookie will look something like:
The cookie value (the part after the
=) is split into 2 parts, separated by
--. The first part is a Base64 encoded serialization of the hash that Rails will use as the
session variable in controllers. The second part is a signature created using
secret_token, that Rails uses to check that the cookie it has been passed is legit. This prevents users from forging nefarious cookies and from tricking Rails into loading data it doesn’t want to load. Unless of course they have your
secret_token and can also forge the signature…
The first part of the cookie is a Marshal dump of the session hash, encoded in Base64. Marshal is a Ruby object serialization format that is used here to allow Rails to persist objects between requests made in the same session. In many cases it will only store the
_csrf_token and Warden authentication data, but calling
session["foo"] = "bar" in your controllers allows you to store pretty much anything you want. For my cookie above, unescaping the URL encoding and then Base64 decoding gives:
which if you squint hard enough is indeed starting to look kind of like a hash. This cookie is passed up to the server with each request, Rails calls
Marshal.load on it, and merrily populates
session with whatever serialized objects it is passed. Object persistance between requests. Brilliant.
But wait, the cookie obviously lives on the client side, which means that a user can set it to be anything they want. Which means that the user can pass in whatever serialized object they want to our app. And by the time we reinflate it and realise that they have passed us a small thermonuclear device, it will be too late and the attacker will be able to execute arbitrary code on our server.
That’s where our
secret_token and the second part of the cookie value (the part after the
--) come in. Whenever Rails gets a session cookie, it checks that it hasn’t been tampered with by verifying that the HMAC digest of the first part of the cookie with its
secret_token matches the second, signature part. This means in order to craft a nefarious cookie an attacker would need to know the app’s
secret_token. Unfortunately, just being called
secret_token doesn’t make it secret, and, as already discussed, if you aren’t careful then it can easily end up somewhere you don’t want it to.
If you know an app’s
secret_token and want to forge a valid cookie, you simply need to reverse the above process:
This request will load
session, which is purely on principal not good. But loading arbitrary strings and integers is not about to melt any servers. How can you choose the contents of your hash so as to actually do some damage? Some obscure objects buried deep inside Rails are happy to oblige:
ALL of this trouble can be trivially avoided by taking
secret_token out of your version control. Put it into an environment variable (dotenv is handy for local, it’s easy on Heroku too) and you can sleep (a bit more) soundly at night. If you suspect that someone you wouldn’t want to meet on a dark night knows your
secret_token then you can simply change it. All your existing cookies will be invalidated, but nothing else bad will happen. Of course, you still don’t want anyone you don’t trust to get any kind of access to your codebase at all. But you can at least make life difficult for them even if they do.