Mass Assignment Attacks in Rails

Building write capable endpoints is a lot of like defending a castle. This is primarily because, as an old saying in software development goes: all user input is suspect.

While it might be unsettling to think your application users are trying to destroy the wonderful things you've created for them (the ungrateful bunch!) there is a rationale for this healthy dose paranoia. Whether an incorrect input is ignorance or malevolence, the reality is if you don't protect your application write capable endpoints bad things are bound to happen. Because of reality, since the release of Rails 4, the framework introduced a concept called "Strong Parameters" to help developers be more conscious of input filtering.

Strong parameters is a practice of pre-filtering inbound request input in order to block unexpected input from getting to controller business logic. The technique has its origins in the exploitation of mass assignment attacks in the wild pre-Rails 4. Mass assignment is a Rails application vulnerability wherein a developers fails to explicitly permit and deny attributes in request input, and a malicious actor exploits this by injecting arbitrary attributes into an ActiveRecord object. Let's take a look at an example to get an idea.

Mass Assignments in Codespace

Suppose you run a successful social coding website called Codespace. Users love it and enterprises are paying big SaaS dollars to get in on the social coding action.

Unfortunately, during the bootstrapping phase of your soon-to-IPO company, you made the decision to have the following database schema and controller structure:

create_table "users", force: :cascade do |t|
  t.string "email"
  t.boolean "admin"
end

class UsersController 
  def create
    User.create!(params[:user]) ❶
  end
end

Doesn't seem too bad, right? It looks like in the UsersController create action, the params hash is believed to have all the required attributes to create a User and is therefore used to create one through mass assignment ❶:

{ params: { user: { email: "niceuser@email.com"} } }

The issue with the code as written is that it is vulnerable to mass assignment attacks. In your late nights of irresponsible Redbull fuelled dubstep coding sessions you have failed to consider if unexpected attributes are present in the params hash that could have high exploitation worth.

A security researcher enters the picture and issues an identical POST request with one fatal difference:

{ params: { user: { email: "niceuser@email.com", admin: true} } }

Suddenly the researcher has obtained unauthorized administrative privileges which can be used to do bad things like introducing a new vulnerability or delete code repositories! Not good - Wall Street isn't going to like this!

Investors overreact and believe your product is fundamentally flawed and scramble to sell the stock on secondary market. Your employees begin a revolt  – destroying their laptops and deleting all the company IP from your internal servers. The bankers calls to tell you they are freezing your accounts because they just don't like you anymore. And your co-founder tells you they never really loved you. Your dreams of making social coding for all the children across the world are crushed, and it's all because of simple oversight in your controller. How could you be so careless?!

Joking aside, a similar but more complex version of this attack was exploited historically on GitHub in the Rails codebase. After a security researcher raised an issue about mass assignment and the lack of framework enforced mitigations, they proceeded to inject a playful, but, benign commit into the Rails master branch. This was startling and compelling because the researcher did not have access credentials to make changes, but, was still able to do so. It didn't crush the dreams of anyone but it did raise serious concerns about the value of exploiting such attack vectors – motivating the introduction of strong parameters into the framework.

Mitigation with Strong Parameters

To prevent exploitation of mass assignment vulnerabilities we use so-called "strong parameters" to explicitly allow a subset of keys in a hash and filter out everything else. Let us revisit an example with the introduction of strong parameters:

class UsersController 
  def create
    User.create!(create_user_params)
  end

  private
  def create_user_params
   params.require(:user ❶).permit(:email) ❷
  end
end

In this instance, we have created a helper method create_user_params which makes use of require to ensure that the user hash key is present ❶ in the params hash. If the key is not present an ActionController::ParameterMissing error will be raised. After locking in required hash keys we proceed to permit a shortlist of nested keys within that key entry.

For example, the create_user_params permits that only a single nested key email from the user hash. This means that any additional key value pairs in the user hash, such as an admin: true will be filtered out.

{ params: { user: { email: "niceuser@email.com", admin: true } } }

The result of the request after strong parameters is a single email key which is safe to create a new user with and prevents the privilege escalation demonstrated in the example:

User.create!({ email: "niceuser@email.com" })

Strong parameters is a simple technique but was a great addition and security win for developers in the end!

Happy trails!