Advertisement
Ruby

Mass Assignment, Rails, and You

by

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 User 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 User 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 update action within the UsersController 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 params 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 :admin, :owner, and :public_key, are quite easily guessable.

When this request hits our update action, the update_attributes call will see {:can_fire_missiles => true}, 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 :admin, :owner, and :public_key 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: attr_protected and attr_accessible.

attr_protected: The BlackList

Using attr_protected, 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 can_fire_missiles attribute will fail.

attr_accessible: The WhiteList

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

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

As such, we can switch our User 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 admin flag to the User model, it will automatically be safe from mass-assignment.

As a general rule, you should prefer attr_accessible to attr_protected, 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 attr_protected and attr_accessible 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 config.active_record.whitelist_attributes setting within the config/application.rb file.

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

If set to true, mass assignment will be impossible for all models unless they specify an attr_protected or attr_accessible 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: config.active_record.mass_assignment_sanitizer.

If set to :strict, it will raise an ActiveModel::MassAssignmentSecurity::Error 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 roles 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/application.rb:

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 user.update_attributes(params), you'll get an error in your application. You must first call permit on the params 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!

Related Posts
  • Code
    Ruby
    Writing Robust Web Applications - The Lost Art of Exception HandlingRails education retina preview2
    As developers, we want the applications we build to be resilient when it comes to failure, but how do you achieve this goal? If you believe the hype, micro-services and a clever communication protocol are the answer to all your problems, or maybe automatic DNS failover. While that kind of stuff has its place and makes for an interesting conference presentation, the somewhat less glamorous truth is that making a robust application begins with your code. But, even well designed and well tested applications are often lacking a vital component of resilient code - exception handling.Read More…
  • Code
    Ruby
    Getting Started With New Relic in 30 MinutesGetting started new relic retina preview2
    I remember working on a Rails app a few years ago and someone floated the idea of using this new service that had appeared on the scene. It was called New Relic and they were promising to give you more insight into the performance of your Rails app, than you ever could get before. We gave it a try and it was impressive, more importantly it was something the Ruby web development ecosystem truly needed.Read More…
  • Code
    Ruby
    Digging Into Rails 4Digging rails
    Rails 4 is rapidly approaching. In this article, let's take a look at some of the new features that it offers, as well as the changes that may affect your current applications.Read More…
  • Code
    Ruby
    Active Record: The Rails Database BridgeRelational databases for dummies preview
    In the past, to build a web application, you required the skills to code in your business logic language and your database language. More recently, however, back-end frameworks are leaning toward using Object-Relational Mapping (ORM); this is a technique that lets you manage your database in the business logic language that you're most comfortable with. Rails uses an ORM in the form of Active Record. In this tutorial, we'll dive into Active Record and see what it can do for us!Read More…
  • Code
    PHP
    Build Your First Admin Bundle for LaravelLaravel bundles
    It's hard to deny the fact that the PHP community is excited for Laravel 4. Among other things, the framework leverages the power of Composer, which means it's able to utilize any package or script from Packagist. In the meantime, Laravel offers "Bundles", which allow us to modularize code for use in future projects. The bundle directory is full of excellent scripts and packages that you can use in your applications. In this lesson, I'll show you how to build one from scratch!Read More…
  • Code
    Ruby
    Why Rails?Why rails
    Your choice, when learning a new framework, is an incredibly important one. It takes countless hours and effort to become proficient and learn all the best practices - even for experienced developers. That's why it's necessary to understand the peculiarities of a framework as early as possible, in order to determine if it's the right solution for the problem that you're trying to solve. In this article, I'll cover many of the key areas of the Ruby on Rails framework, and why I feel that it's an excellent choice for web developers.Read More…