Why You Need Strong Parameters in Rails

Why You Need Strong Parameters in Rails

In 2012, GitHub was compromised by Mass Assignment vulnerability. A GitHub user used mass assignment that gave him administrator privileges to none other than the Ruby on Rails project. In this post, I will explain this vulnerability and how you can use the Rails strong parameters API to address it.

5 min read

You’ve used strong parameters in your Rails applications, but did you know what problem they are solving? I didn’t. So did some reading and learned about a common security vulnerability. In this post, I will explain the Mass Assignment vulnerability and how you can use the Rails strong parameters API to address it.

Mass Assignment Vulnerability

What is it? Mass Assignment means assigning values to multiple variable or object properties at a time.

Why is it bad?

Consider the following object:

user = {
  name: "Jason",
  company: "37signals",
  owner: true
}

Now let’s say we want to update the company’s name to “Basecamp”. The typical web-application way to do this would be to receive some data, preferably from a form submission from the user, and use that data to update our user’s properties.

params = {
  # some data
  company: "Basecamp",
  # some data
}

user.update!(params)

Boom! You are done. But wait, the next day, Jason comes to the office and realizes there’s a new owner. He’s not listed as the owner anymore of the company.

How did that happen?

The answer is the other properties in the params object above that I didn’t list. Here’s what the complete object looks like:

params = {
  name: "Jason",
  company: "Basecamp",
  owner: false
}

Because the software updated all the properties of the user using the params hash in its entirety, the owner property was updated to false. As a result, Jason is not an owner anymore.

Now, of course, this is a very simple example to illustrate the problem. Of course, the web application is not foolish enough to ask whether the owner is indeed the owner on a front-end form that anyone can submit, and thankfully, Jason is still the owner.

But you get the idea. It’s terrible to update the properties of an object from a source that we don’t trust!

Mass assignment vulnerability occurs when your application assigns the data from a user input to multiple objects, variables, of database fields at once. Updating the object’s properties allows an attacker to modify or overwrite the existing data.

If you want to see a real-world example, in 2012 GitHub was compromized by this vulnerability. A GitHub user used mass assignment that gave him administrator privileges to none other than the Ruby on Rails project. As GitHub co-founder, Tom Preson-Werner later said,

“The root cause of the vulnerability was a failure to properly check incoming form parameters, a problem known as the mass-assignment vulnerability,”

How to prevent it?

The solution is simple. Before you update any object, filter out only the properties you want and nothing else!

class UsersController < ActionController::Base
  def create
    User.create(user_params)
  end
  
  def update
    User.find(params[:id]).update_attributes!(user_params)
  end

  private
    def user_params
      params[:user].slice(:name, :company)
    end
end

This pattern was so common that Rails released a plugin as well as a gem for it (strong_parameters), and later versions of Rails (v4 onwards) provide it out of the box.

Here’s how it works.

How Strong Parameters Work

The philosophy behind strong parameters is assume unsafe until proven otherwise. In simple terms, Rails marks the parameters forbidden to be used in mass assignment until you explicitly mark them as safe.

How do you mark parameters as safe?

Using the require and permit methods on the params hash, which is an instance of ActionController::Parameters.

params.require(:client).permit(:name, :company)

In the above code, we explicitly mark the client parameter as required using the require method, and only permit the name and company parameters inside the client. If the client parameter has an admin parameter, Rails won’t allow you to use it in a mass assignment operation.

Let’s open the Rails console by running bin/rails console and run some experiments:

params = ActionController::Parameters.new({
  client: {
    name: "Jason",
    company: "Basecamp",
    admin: true
  },
  user: {
    name: "David"
  }
})

=> #<ActionController::Parameters {"client"=>{"name"=>"Jason", ..}} permitted: false>

Yes, you can simply create a params object on fly. You don’t need to make a request from the browser to access it. Pretty cool, right?

Notice that the output of the above code was an object with the permitted property set to false. Now, let’s mark the client parameter as required, only permitting the name and company parameters.

client_params = params.require(:client).permit(:name, :company)
=> #<ActionController::Parameters {"name"=>"Jason", "company"=>"Basecamp"} permitted: true>

Note that after validating the parameters and allowing only the attributes we want, the result client_params has the permitted property set to true. The client_params parameter is now safe to use in a mass-assignment operation.

To permit an entire hash of parameters, use the permit! method.

params.require(:client).permit!
💡
Remember that the strong parameters are only validated when you try to use them to update the active model’s properties. It won’t prevent you from accessing the data.
> params
=> #<ActionController::Parameters {"client"=>#<ActionController::Parameters {"name"=>"Jason", "company"=>"Basecamp", "admin"=>true} permitted: false>, "user"=>{"name"=>"David"}} permitted: false>

# This works
params[:user]
=> #<ActionController::Parameters {"name"=>"David"} permitted: false>
params[:user][:name]
=> "David"

# This doesn't
User.update!(params[:user])

Nested Parameters

You can use strong parameters on nested params, as shown below:

params = ActionController::Parameters.new({
  product: {
    name: "iPhone",
    price: 500,
    accessories: [{
      name: "headphone",
      price: 35
    }, {
      name: "adapter",
      price: 90
    }]
  }
})

permitted = params.permit(product: [ :name, { accessories: :price } ])
permitted.permitted?                            # => true
permitted[:product][:name]                      # => "iPhone"
permitted[:product][:price]                     # => nil
permitted[:product][:accessories][0][:price]    # => 35
permitted[:product][:accessories][0][:category] # => nil
💡
Note: If you permit a key pointing to a hash, Rails doesn't allow the entire hash. You must specify the permitted hash attributes.

Permit All Parameters

If you want to permit all parameters by default, Rails provides the permit_all_parameters option, which is false by default. If set to true, it will permit all the parameters (not recommended).

ActionController::Parameters.new
=> #<ActionController::Parameters {} permitted: false>

ActionController::Parameters.permit_all_parameters = true
=> true

ActionController::Parameters.new
=> #<ActionController::Parameters {} permitted: true>

Take Specific Action for Unpermitted Parameters

You can also control the behavior when Rails finds the unpermitted parameters. For this, set the action_on_unpermitted_parameters property, which can take one of three values:

  • false to do nothing and continue as if nothing happened.
  • :log to log an event at the DEBUG level.
  • :raise to raise an ActionController::UnpermittedParameters exception.
params = ActionController::Parameters.new(name: "DHH")

### false ###
ActionController::Parameters.action_on_unpermitted_parameters = false
params.permit(:rails)
=> #<ActionController::Parameters {} permitted: true>

### log ###
ActionController::Parameters.action_on_unpermitted_parameters = :log
params.permit(:rails)
2022-05-17 18:39:23.099411 D [56269:4120 subscriber.rb:149] Rails -- Unpermitted parameter: name
=> #<ActionController::Parameters {} permitted: true>

### raise ###
ActionController::Parameters.action_on_unpermitted_parameters = :raise
params.permit(:rails)
=> #../metal/strong_parameters.rb:1002:in `unpermitted_parameters!': found unpermitted parameter: :name (ActionController::UnpermittedParameters)

All right, that’s enough for today. I hope you now have a better understanding of why strong parameters exist in Rails and how you can use them to build safe and secure applications.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.