MarkUs Blog

MarkUs Developers Blog About Their Project

Rails 4 Strong Parameters

with one comment

A long time ago the developers of Rails wanted a simple API for creating new instances of model objects. What they came up looks something like this:

User.new({ user_name: ‘markus’, last_name: ‘Us’, first_name: ‘Mark’, grace_credits: 5, section_id: 2, type: ‘Student’ })

 

This form of initialization, with a hash, is referred to as mass assignment. Using a hash as input to the constructor made the initialization process very flexible and it was used for seeding the database and creating mock objects for testing.

The problem with having something as nice as mass assignment is that it ended up being used for everything. It became (too) common for controller code to be written that would initialize an object by taking input straight from web forms:

def create
    # Default attributes: role = TA or role = STUDENT
    # params[:user] is a hash of values passed to the controller
    # by the HTML form with the help of ActiveView::Helper::
    @user = Student.new(params[:user])
    if @user.save
      flash[:success] = I18n.t(‘students.create.success’,
                               :user_name => @user.user_name)
      redirect_to :action => ‘index’ # Redirect
    else
      @sections = Section.all(:order => ‘name’)
      flash[:error] = I18n.t(‘students.create.error’)
      render :new
    end
  end

 

Or, at least you hoped the params were coming from a web form, and that the person filling in the web form didn’t add some extra data you weren’t expecting. In reality, a malicious user could construct their own POST request to the server and create a new user that had administrative powers. This actually happened to Github a couple of years ago.

This kind of exploit is a text book example of incomplete mediation. Checking the validity of user input on the client side does little to make the app secure. An attacker has full control of the client and can manipulate input to the server in any conceivable way. User side checking may be valuable for user experience reasons, but the checks must also be performed again on the server where we have control. More information on the principles of security for protecting computer information can be found here.

Shortly after the Github/Rails issue, the Rails core team wanted safe defaults for new projects. At the time, the existing Rails solution for this issue was to declare model attributes as being mass assignable or not mass assignable. In code, you would declare attributes as attr_accessible (whitelist) or attr_protected (blacklist), and these declarations would be used to maintain a whitelist of attributes which could be set using mass assignment. However, whitelisting was not enabled by default, and so in Rails 3.2.8, all new apps included the following configuration:

config.active_record.whitelist_attributes = true

Which caused all models to start with an empty whitelist. Mass assignable attributes had to be explicitly listed with attr_accessible. Safety by default was implemented, but it came at the cost of being able to use mass assignability for legitimate purposes. The Rails security guide from that era includes more details on the whitelist configuration and how to work with its limitations.

The problem with having all models disallow mass assignability was that programmers would have to go to more trouble to seed databases, generate mock objects, etc. Possibly for this reason, MarkUs never turned on default whitelists.

The logic was that the whitelists would stop incomplete mediation attacks from anywhere that models are created. However, these attacks come from user input, which is first handled (on the server side) by controllers. So, performing sanitization checks in the controller would limit the impact of the whitelists to where it mattered.

During Rails 4 development, the core team removed attr_accessible and the entire mechanism for whitelisting attributes in model classes. The new solution, strong_parameters, enforces attribute whitelists on the input params before passing the data along to the model constructor. Since strong_parameters does not directly affect model code, it is once again easy to use mass assignment for internal machinery and trusted scripts.

MarkUs predates Rails 3.2.8, and the whitelist default was never turned on during the upgrade to Rails 3.2. MarkUs does use attr_accessible in a couple of places, and avoids using mass assignment for input coming from students. However, the example controller code above (that demonstrates the incomplete mediation flaw) comes straight from the MarkUs code base as it exists today. The example is actually fairly safe because it requires an authorized admin to make the POST in the first place, but nonetheless demonstrates unsafe practices.

I think that current strategy for handling student input entirely without mass assignment is the best strategy from a security perspective. This would also make the upgrade to strong_parameters painless.

It is not recommended to switch to strong_parameters and perform the Rails 4 upgrade at the same time. However, strong_parameters is available for Rails 3.2 as a back port and we can migrate to it before the upgrade to Rails 4. I think it would be best to switch to strong_parameters right away and move away from using mass assignment when the input comes from the client side.

A blog post demonstrating how strong_parameters works can can be found here. The API documentation for strong_parameters can be found here.

Written by Mark Rada

June 11th, 2014 at 11:53 am

Posted in Uncategorized

One Response to 'Rails 4 Strong Parameters'

Subscribe to comments with RSS or TrackBack to 'Rails 4 Strong Parameters'.

  1. Great post! It outlines the problem very clearly, and your approach to handling it makes sense to me.

    Markus

    11 Jun 14 at 1:51 pm

Leave a Reply