Hacking Rails (and GitHub)

05 Mar 2012

Hacker News exploded yesterday with news of GitHub being hacked. Wanting to know what all the fuss was about, I began with GitHub's side of the story:

A GitHub user exploited a security vulnerability in the public key update form in order to add his public key to the rails organization. He was then able to push a new file to the project as a demonstration of this vulnerability.

As soon as we detected the attack we expunged the unauthorized key and suspended the user.

My confidence in the clarity of GitHub's side of the story dissipated when I read one of the comments:

You didn't really "detect" anything. You were informed. It also wasn't an attack.

Not only did the "attacker" not do any actual damage, but he was continually ignored.

The author of this comment, Chris Acky, also made a more comprehensive blog post about the incident.

The "attacker" in question is Egor Homakov (his account has been reinstated), and he did in fact disclose the vulnerability a few days before he demonstrated it. There are a few facts worth noting up front:

Telling someone they're wrong only fuels their desire to prove they're right. It's not a huge surprise that Egor's next step was to demonstrate the vulnerability.

I'd like to explain the vulnerability, but rather than show you any code, I want you to understand the nuts and bolts, because it's extraordinarily simple. If you have a GitHub account, you can manage your SSH keys. The form to add a new key looks something like this (edited for clarity):

  1. <form method="post" action="/account/public_keys">
  2.   <input type="hidden" value="412e11d5317627e48a4b0615c84b9a8f" name="authenticity_token" />
  3.   <dl>
  4.     <dt>Title</dt>
  5.     <dd><input type="text" name="public_key[title]" /></dd>
  6.     <dt>Key</dt>
  7.     <dd><textarea name="public_key[key]" /></dd>
  8.   </dl>
  9.   <input type="submit">Add key</input>
  10. </form>

The authenticity_token is almost certainly an anti-CSRF token, so it doesn't complicate this exploit at all.

All Egor did was modify his own form to add the following:

  1. <input type="hidden" name="public_key[user_id]" value="4223" />

The user_id of the Rails project is 4223, so that's why he chose it. (He believes this is a Rails issue, and it's hard to argue that.) By sending along this user_id, his public key was added to another account. Yikes!

For those of you more familiar with PHP, imagine a feature like register_globals, but instead of injecting arbitrary form data into the global namespace, it injects arbitrary form data into the database. It might as well be called opt-in SQL injection, but even that's being too generous, because this is much easier to exploit than an SQL injection vulnerability.

Egor points out that this vulnerability is unique to Rails:

Only Rails app have this kind of bug.

Wanting to better understand why Rails refuses to fix this, I looked into mass assignment, the feature in question, and found a post from last year:

If you're using Rails and you want to be secure, you should be protecting against mass assignment. Basically, without declaring attr_accessible or attr_protected, malicious users can set any column value in your database, including foreign keys and secure data.

While it's unfair to expect Rails to prevent mistakes, this does seem like a clear case where it promotes insecurity. As one person commented:

Rails is all about conventions. Broken by default is not a good convention.

Yehuda Katz has proposed a solution that people seem to like. Here's hoping this event can help raise the bar for what's expected of a framework. When Zend Framework was first announced, I made a wishlist, because I think frameworks are perfectly suited to help developers write more secure code. Rails is no exception.

If you're interested in reading more about this, here are some links:

Mass assignment vulnerability - how to force dev. define attr_accesible?
The GitHub issue where Egor first discloses the vulnerability.
wow how come I commit in master? O_o
Egor's commit to Rails, demonstrating the vulnerability.
Responsible Disclosure Policy
GitHub's followup post that mentions their new responsible diclosure policy.
"Egor, stop hacking GH"
Egor's original post, describing vaguely what he has been able to do and citing the fact that he feels ignored.
i'm disappoint, github
Egor's second post, where he proclaims his love for GitHub and disappointment with their response, suspending his account.
Egor's final post, revealing the details of the exploit.
Comment by Max Bernstein
References an email conversation with Egor, where Egor claims that he emailed GitHub about the vulnerability and received no response.
How Homakov hacked GitHub & the line of code that could have prevented it
Gist from Peter Nixey that explains a single line of code that can prevent this vulnerability in your own Rails apps.