Techniques to Secure Your Website with Ruby On Rails (Part 2)
Last week we looked at ways malicious people can try and hijack the sessions of valid users. This week, we’re going to look at two dangers you are faced with when a malicious user signs up to your site. We’ll again look at how Rails protects you, and how you can protect yourself further.
Changing Unexpected Attributes
On 4th March 2012, a vulnerability in Github was successfully exploited, allowing a malicious user to get commit access to the Rails repository. The hack was a mass-assignment vulnerability – a common problem in most Rails applications I have seen. The hack is extremely easy to implement and there is a good chance you have this security hole in your applications.
This is such a big vulnerability and such a simple fix – why not take some time and update your applications today?
Let’s look at this hack in detail. We will start with a basic model, controller and form:
[gist id=”2905484″]
[gist id=”2905498″]
[gist id=”2905506″]
This is a very standard pattern. The controller expects params[:user]
to be something like {:username => 'iHiD'}
. The controller updates the model with the new username and then redirects to a show page.
Now, let’s say I’m a sneaky hacker who wants admin access to this site. I craft a request that sends the data {:username => 'iHiD', is_admin: true}
to the application. The controller then obediently updates the @user
with this hash, and I’ve just become an admin. In Github’s case, the malicious user added a public key to the Rails organisation, giving them commit rights.
This is all very bad.
Why doesn’t Rails protect me from this, you scream! Well, it does – it provides you with all the tools to fix this, but it’s up to you to enforce it. Rails provides two options in your model to protect attributes: attr_protected
and attr_accessible
. By adding attr_protected
to your model, you can blacklist attributes that should not able to be mass assigned. For example:
[gist id=”2905615″]
Any attempt to set is_admin
via update_attributes
will now result in an error. This is a step in the right direction, but in my opinion, it’s not good enough. Blacklisting elements is highly vulnerable to user error. You need to remember to update this feature everytime you create a new attribute. In addition, there is one little-known further issue with assign_attributes
which most developers don’t know. Not only are attributes assignable though that function – methods are vulnerable to mass-assignment too. Here’s a contrived example:
[gist id=”2905710″]
This code sets can_do_dangerous_things
to be set to false by default and protects us from directly accessing it. However, it doesn’t protect our helper method!
[gist id=”2905722″]
So, while attr_protected
helps, it only goes so far. Instead, we should use attr_accessible
to whitelist elements. In fact, after the Github hack, Rails released 3.2.3, which changed the a configuration option to force attr_accessible
by default. If you created your app before 3.2.3, then set the following in your config:
[gist id=”2905738″]
This will only allow attributes specified in attr_accessible
to be accessed through mass assignment. We’d can now change the User model to:
[gist id=”2905743″]
This fixes the security hole, but it adds a problem if you internally use update_attributes
to set attributes that you don’t want users to be able to mass-assign. To solve this, you can set up roles that for attr_accessible
. I tend to add a role called :internal
that has a larger range of accessible attributes for batch updates or internal functionality. To do this on our User model, do the following:
[gist id=”2905770″]
I can now do this:
[gist id=”2905776″]
Our code is now safe, and we’ve not created any limitations for ourselves. Win!
As I said at the beginning, this is such a big vulnerability and such a simple fix – why not take some time and update your applications today?
Safe Database Queries
SQL injection is a well-documented topic, and well understood by most developers. Rails makes it simple to avoid but, if you’re not careful, you can remove all of that protection. To quickly summarise, a malicious user can craft clever SQL that is passed as a parameter to your application, and maliciously executed. As an example, let’s say you have a page that allows users to search through their projects. A user visiting /projects?name=rai
hits the following code and gets all their projects starting with “rai”:
[gist id=”2905924″]
Let’s say I’m a malicious user who wants to hack your code, I could visit
/projects?name='%20OR%20created_at%20LIKE%20'%
, which would execute the following SQL:
[gist id=”2905941″]
Suddenly I can see everyone’s projects! This is a bad thing.
Let’s start by fixing that vulnerability – it’s simple to do. Rails protects us from SQL injection if we use it’s helper methods rather than strings. There are three safe methods that we can use: a hash, placeholders and bind variables. Here are three examples:
[gist id=”2906015″]
So let’s update our searching code. For the first part with user_id, we can use a hash. However, we’re using LIKE for the second part so we cannot use a hash, and bind variables seem unnecessary if we’re only using one variable, so let’s use a placeholder. Here’s our updated code:
[gist id=”2905973″]
However, we can take this one step further. As the project
has a user_id
, in our User
model, we will have a has_many :projects
. We can take advantage of this by using the association in our query and removing one of the where
conditions. Let’s update our code:
[gist id=”2906035″]
We’ve now protected our code against SQL injection, made it more readable and more maintainable. If you ever see SQL with interpolation in your Rails code, consider it to be a massive code smell and address it immediately.
Further Reading
This article has explained two crucially important vulnerabilities and how to protect against them. However, there are lots of other things you need to be aware of when developing Rails applications that I haven’t covered here, so make sure you read the Rails Security Guide to learn more.
Next week, in the final part of the series, we’ll look at how you protect the views that you render, and explore a couple of other general issues of which you should be aware.