Ruby on Rails Known Secret Session Cookie Remote Code Execution
https://www.rapid7.com/db/modules/exploit/multi/http/rails_secret_deserialization
How to hack a Rails app using its secret_token
22 Jul 2013
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.
Why your secret_token
is important - session cookies
Your 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 serialized session hash
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 thesession_id
, _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.The signature
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.The attack - how to bake a poisonous cookie
If you know an app’ssecret_token
and want to forge a valid cookie, you simply need to reverse the above process:This request will load
my_evil_session_hash
into 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:And presto.
Knowing is half the battle
ALL of this trouble can be trivially avoided by takingsecret_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.Thanks to
Rails Security by Code Climate and TechBrahmanaDiscussion on Hacker News
Get more posts like this:
It looks like you should be able to do this:
request = ActionDispatch::Request.new(env)
request.cookie_jar.signed[:user_id] #=> 1
You can check out .../action_dispatch/middleware/cookies.rb on github to read more about exactly what is going on.
No comments:
Post a Comment