Ruby On Rails Allow Mass Assignment

Mass Assignment and Security in Ruby on Rails

By Jeff Hunter

Monday, August 3, 2009

The topic of security vulnerabilities exposed through in ActiveRecord::Base recently came up on the SF Ruby Meetup list. The related blog entry can be found here. It’s an important question about Rails, and one that a little information can help clarify what to worry about and what not to worry about.

First off, for anyone not familiar enough with Rails, what is ?. It’s the magic method that allows mass assignment of attributes from a hash. It’s what lets you do things like the following with your active record classes:

The method takes each key in the hash, and as long as your model provides a mutator method for that attribute (responds to “key=”, and, of course, each column in your table gets one of these mutators created by default). However, before begins processing the hash, it first passes it to (unless you’ve called with set to false). By default, this will exclude the primary key column, the inheritance column (e.g. type), and the column named (if your primary key has another name). Everything else is fair game.

Protecting Attributes

For simple models, the default behavior may be just fine. You don’t have to worry about your or columns getting changed, and anything else can be easily used with . Things can get tricky, though, once you start adding in some of the extra goodies in Rails. Let’s say, for example, that I have a table. I have a simple model that just inherits from and I’ve decided to add and fields to get some automatic time stamping behavior. Let’s say the update action in my controller looks like this:

Here, any HTTP client can pass me a parameter named and my creation time will be set to whatever value is passed in (any value passed in for will be overridden when the record is saved, so it’s not exposed in this particular scenario). That’s probably not what I want. I probably only want to allow the creation time to be set when the record is first saved.

There are a few different ways to protect my attribute. ActiveRecord::Base provides two mechanisms for controlling which attributes are weeded out by . You can use to specify a list of additional attributes which may not be used with (a black list). Alternatively, you can use to specify an exclusive list of attributes that may be used with (a white list). If you use , any attribute not in the list you specify cannot be used with mass assignment. Lastly, in this case we could also just mark as a read-only attribute. Read-only attributes may be mass assigned when calling new, but not through other means. So, either one of these two additions to our model will prevent surprises happening to :

Why doesn’t Rails just protect these magical attributes by default? In my view, what protection you need for the time stamp fields, if any, is really application-specific. While most applications probably don’t want to allow these fields to be set outside of the model itself, that’s not always the case. In the case of my BlogPost example, if I were to add functionality allowing posts to be imported from other blogs, there might be a legitimate case where I want to allow to be explicitly set (to preserve the value from the imported post). Leaving this decision to the application developer ultimately provides the most flexibility, but it does require some awareness of the consequences when using mass assignment.

Associations

A forged value isn’t terribly dire for most applications, but plugins and associations can add new assignment methods to your model, some of which may cause much bigger security problems. Associations in particular can expose some security vulnerabilities for the naïve programmer. Coming back to my blog post example, let’s say I want to track the author of each post. I have a User model, and, for simplicity, I’m just going to let the author be tracked as the user property on a blog post:

With no modification to my controller, I could wind up with a very nasty surprise if an HTTP client were to pass in a parameter. In effect, it could allow anyone to spoof the author of a post or corrupt the data in my table by specifying an invalid user id. In this example, there’s really no case where I want to allow unchecked mass assignment to the property, so changing my model to prohibit that will provide some added protection:

That leaves me with the issue of how to legitimately set the author on a blog post. For the sake of simplicity, let’s say I have the current user id stored in the session. We’ll imagine my application controller takes care of preventing users that haven’t logged in from accessing my controller, so I don’t have to worry about the id not being set. (Perhaps not the best implementation, but, again, it keeps this example simple.) Lastly, in my application I consider the author to be the user who creates the post, not any subsequent users who edit it, so I’ll do this work in the create method:

Here I’m actually using the user assignment method created by the association. I could assign directly to the attribute, but for the cost of an additional database call, I’ll get an exception if I try to assign an invalid id. Whether that’s really worth doing or not depends and involves using some of your own judgement. What gets highlighted here, though, is yet another assignment method we’ve acquired. Do we need to worry about protecting the method as well? You certainly can protect it the same way. However, that method will throw an AssociationTypeMismatch exception if you try to assign anything other than a User model instance, which provides some protection from the kind of attack that is vulnerable to. The one exception to this kind of type-based protection is classes which have association collections (reference many associated objects of the same type). The methods that are added by and could be assigned to through mass assignment using HTTP parameters, so some thought should be put into whether or not to protect them in your application.

Other than using , there is another way you could potentially safeguard from malicious HTTP clients, and it builds on the kind of precaution in the previous controller example. You could simply validate the attribute. Here’s what that might look like:

This, of course, doesn’t prevent an HTTP client from setting the (unless the controller always explicitly assigns the user after a mass assignment), but it does prevent assigning an invalid user id, whether by the HTTP client or application code using the model, and it some applications that may be sufficient. It also has the added draw of containing the safeguards specific to a model within the model instead of letting them leak out into the controller.

At about this point in the discussion, I should point out that one blog entry referenced in the original thread has a very different take. The author of that post argues for using for every model, always. That’s certainly available in ActiveRecord for you to do that if you choose, but I fundamentally disagree with that as an approach and I feel it runs counter to the spirit of Rails. It solves the problem of potentially exposing an attribute for mass assignment without intending to do so, but at the cost of quite a bit of tedium on any sizable application. You are forced to enumerate all columns which can be used with mass assignment (which may well be most) for all tables and to update the list for every schema change. That’s highly error prone, and part of the whole point of the ActiveRecord approach is not configuring your entire schema in both the database and the model.

Context Sensitive Attributes

In many real-world applications it’s not just a matter of whether or not to allow an attribute to be assigned to, but when and who is allowed. In my blog example, I may decide that administrators are allowed to post as other users and can choose the author from a drop-down list. Naturally, I wouldn’t want to allow non-administrators to do so. In other words, the context of the assignment matters. In fact, this was the underlying issue of the original message in the thread. Easily enough you could limit mass assignment to the subset of attributes which can always be assigned to in any context and then manually deal with the exceptions in the controller. In my example that might look something like:

I’m assuming here that I’ve added a method to the application controller that returns the user object for the currently logged in user. This handles the issue, but it’s not particularly elegant, and can quickly get unwieldy if, as in actual applications, many of these exceptions occur in many different places.

The temptation is to try to somehow make all the magic happen within the model. Another list member wound up blogging about this very issue here. I agree with his assessment that the model really isn’t a place to put that kind of contextual knowledge. Contextual information belongs in the controller. He suggests an approach that allows lists of sensitive parameters to be declared in the model and then selectively used to filter parameters by the controller. I think an ideal situation would allow the user to declare multiple filtering lists in the model and then selectively choose them from the controller, possibly applying them as part of a mass assignment call. And, of course, validation should still be applied for sensitive bits like foreign keys. Unfortunately, as far as I know, no plugin yet exists to do all of that magically.

So, to summarize a bit, I really see the question that spawned all of this to be touching on two slightly different issues: general security/data integrity issues associated with mass assignment and how to more elegantly handle filtering parameters for mass assignment based on context. Unfortunately, there really isn’t a single best practice for the latter, but I’d certainly be interested in hearing how others address it. I don’t, as some would seem to argue, feel that either or have outlived their usefulness. Even in an application that strictly adheres to a strong division of responsibilities between the model and controller, they make sense for attributes that the model itself may wish to exclude from mass assignment, such as time stamps or other attributes which may have special meaning or behavior.

Early in 2012, a developer, named Egor Homakov, took advantage of a security hole at Github (a Rails app) to gain commit access to the Rails project.

His intent was mostly to point out a common security issue with many Rails apps that results from a feature, known as mass assignment (and did so rather loudly). In this article, we'll review what mass assignment is, how it can be a problem, and what you can do about it in your own applications.


What is Mass Assignment?

To begin, let's first take a look at what mass assignment means, and why it exists. By way of an example, imagine that we have the following class in our application:

# Assume the following fields: [:id, :first, :last, :email] class User < ActiveRecord::Base end

Mass assignment allows us to set a bunch of attributes at once:

attrs = {:first => "John", :last => "Doe", :email => "john.doe@example.com"} user = User.new(attrs) user.first #=> "John" user.last #=> "Doe" user.email #=> "john.doe@example.com"

Without the convenience of mass assignment, we'd have to write an assignment statement for each attribute to achieve the same result. Here's an example:

attrs = {:first => "John", :last => "Doe", :email => "john.doe@example.com"} user = User.new user.first = attrs[:first] user.last = attrs[:last] user.email = attrs[:email] user.first #=> "John" user.last #=> "Doe" user.email #=> "john.doe@example.com"

Obviously, this can get tedious and painful; so we bow at the feet of laziness and say, yes yes, mass assignment is a good thing.


The (Potential) Problem With Mass Assignment

One problem with sharp tools is that you can cut yourself with them.

But wait! One problem with sharp tools is that you can cut yourself with them. Mass assignment is no exception to this rule.

Suppose now that our little imaginary application has acquired the ability to fire missiles. As we don't want the world to turn to ash, we add a boolean permission field to the model to decide who can fire missiles.

class AddCanFireMissilesFlagToUsers < ActiveRecord::Migration def change add_column :users, :can_fire_missiles, :boolean, :default => false end end

Let's also assume that we have a way for users to edit their contact information: this might be a form somewhere that is accessible to the user with text fields for the user's first name, last name, and email address.

Our friend John Doe decides to change his name and update his email account. When he submits the form, the browser will issue a request similar to the following:

PUT http://missileapp.com/users/42?user[first]=NewJohn&user[email]=john.doe@newemail.com

The action within the might look something like:

def update user = User.find(params[:id]) if user.update_attributes(params[:user]) # Mass assignment! redirect_to home_path else render :edit end end

Given our example request, the hash will look similar to:

{:id => 42, :user => {:first => "NewJohn", :email => "john.doe@newemail.com"} # :id - parsed by the router # :user - parsed from the incoming querystring

Now let's say that NewJohn gets a little sneaky. You don't necessarily need a browser to issue an HTTP request, so he writes a script that issues the following request:

PUT http://missileapp.com/users/42?user[can_fire_missiles]=true

Fields, like , , and , are quite easily guessable.

When this request hits our action, the call will see , and give NewJohn the ability to fire missiles! Woe has become us.

This is exactly how Egor Homakov gave himself commit access to the Rails project. Because Rails is so convention-heavy, fields like , , and are quite easily guessable. Further, if there aren't protections in place, you can gain access to things that you're not supposed to be able to touch.


How to Deal With Mass Assignment

So how do we protect ourselves from wanton mass assignment? How do we prevent the NewJohns of the world from firing our missiles with reckless abandon?

Luckily, Rails provides a couple tools to manage the issue: and .

: The BlackList

Using , you can specify which fields may never be mass-ly assignable:

class User < ActiveRecord::Base attr_protected :can_fire_missiles end

Now, any attempt to mass-assign the attribute will fail.

: The WhiteList

The problem with is that it's too easy to forget to add a newly implemented field to the list.

This is where comes in. As you might have guessed, it's the opposite of : only list the attributes that you want to be mass-assignable.

As such, we can switch our class to this approach:

class User < ActiveRecord::Base attr_accessible :first, :last, :email end

Here, we're explicitly listing out what can be mass-assigned. Everything else will be disallowed. The advantage here is that if we, say, add an flag to the model, it will automatically be safe from mass-assignment.

As a general rule, you should prefer to , as it helps you err on the side of caution.

Mass Assignment Roles

Rails 3.1 introduced the concept of mass-assignment "roles". The idea is that you can specify different and lists for different situations.

class User < ActiveRecord::Base attr_accessible :first, :last, :email # :default role attr_accessible :can_fire_missiles, :as => :admin # :admin role end user = User.new({:can_fire_missiles => true}) # uses the :default role user.can_fire_missiles #=> false user2 = User.new({:can_fire_missiles => true}, :as => :admin) user.can_fire_missiles #=> true

Application-wide Configuration

You can control mass assignment behavior in your application by editing the setting within the file.

If set to , mass assignment protection will only be activated for the models where you specify an or list.

If set to , mass assignment will be impossible for all models unless they specify an or list. Please note that this option is enabled by default from Rails 3.2.3 forward.

Strictness

Beginning with Rails 3.2, there is additionally a configuration option to control the strictness of mass assignment protection: .

If set to , it will raise an any time that your application attempts to mass-assign something it shouldn't. You'll need to handle these errors explicitly. As of v3.2, this option is set for you in the development and test environments (but not production), presumably to help you track down where mass-assignment issues might be.

If not set, it will handle mass-assignment protection silently - meaning that it will only set the attributes it's supposed to, but won't raise an error.


Rails 4 Strong Parameters: A Different Approach

Mass assignment security is really about handling untrusted input.

The Homakov Incident initiated a conversation around mass assignment protection in the Rails community (and onward to other languages, as well); an interesting question was raised: does mass assignment security belong in the model layer?

Some applications have complex authorization requirements. Trying to handle all special cases in the model layer can begin to feel clunky and over-complicated, especially if you find yourself plastering all over the place.

A key insight here is that mass assignment security is really about handling untrusted input. As a Rails application receives user input in the controller layer, developers began wondering whether it might be better to deal with the issue there instead of ActiveRecord models.

The result of this discussion is the Strong Parameters gem, available for use with Rails 3, and a default in the upcoming Rails 4 release.

Assuming that our missile application is bult on Rails 3, here's how we might update it for use with the stong parameters gem:

Add the gem

Add the following line to the Gemfile:

gem strong_parameters

Turn off model-based mass assignment protection

Within :

config.active_record.whitelist_attributes = false

Tell the models about it

class User < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection end

Update the controllers

class UsersController < ApplicationController def update user = User.find(params[:id]) if user.update_attributes(user_params) # see below redirect_to home_path else render :edit end end private # Require that :user be a key in the params Hash, # and only accept :first, :last, and :email attributes def user_params params.require(:user).permit(:first, :last, :email) end end

Now, if you attempt something like , you'll get an error in your application. You must first call on the hash with the keys that are allowed for a specific action.

The advantage to this approach is that you must be explicit about which input you accept at the point that you're dealing with the input.

Note: If this was a Rails 4 app, the controller code is all we'd need; the strong parameters functionality will be baked in by default. As a result, you won't need the include in the model or the separate gem in the Gemfile.


Wrapping Up

Mass assignment can be an incredibly useful feature when writing Rails code. In fact, it's nearly impossible to write reasonable Rails code without it. Unfortunately, mindless mass assignment is also fraught with peril.

Hopefully, you're now equipped with the necessary tools to navigate safely in the mass assignment waters. Here's to fewer missiles!

0 Replies to “Ruby On Rails Allow Mass Assignment”

Lascia un Commento

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *