Ruby - - By Jeremy Walker

Techniques to Secure Your Website with Ruby On Rails (Part 2)

Techniques to Secure Your Website with Ruby on Rails

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:

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:

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:

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!

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:

This will only allow attributes specified in attr_accessible to be accessed through mass assignment. We’d can now change the User model to:

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:

I can now do this:

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”:

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:

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:

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:

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:

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.