Posted September 15, 2012 by Spyros in Ruby on Rails

Understanding attr_accessible – Secure Your Rails Application


If you are just starting with Ruby on Rails, chances are that you are using the Devise gem as your authentication system. It’s rather easy to setup and will do just fine for basic authentication needs. Of course, as time goes by and you commit yourself to more Rails project, you will definitely want a more customized solution, in which case i would recommend the Sorcery gem. This post is not about authentication, but i started talking about Devise, because it’s very probable that upon generating a Devise User model, you will have noticed the ‘attr_accessible’ parameter automatically set by the Devise generator. And since i’ve already been there, i can safely say that this was the first time i met this directive, and at the time, didn’t understand why it was there.

What is the attr_accessible directive ?

So, you open up your devise User model, and you notice a line similar to that :

attr_accessible :username, :email, :password

The first thing that is important to know about, if you don’t really, is that attr_accessible is simply an instance method, a function as most non-ruby programmers would name. This is actually a part of ActiveModel::MassAssignmentSecurity::ClassMethods, which already some something about its usage. It’s there to prevent mass assignment security attacks. But what exactly is that ? In Ruby on Rails, a mass assignment is when you save more than one attributes of a model to your database. For instance :



current_user.update_attribute(...)   <- THIS IS NOT A MASS ASSIGNMENT

Notice that update_attribute is not a mass assignment and attr_accessible does not get triggered upon its execution. However, if you execute save to store changes in a model, what Rails does is saving ALL attributes for that model. It does not just save the attribute that you  changed, it saves all attributes. This can be a big problem. Let’s see an example of a simple create action :

  def create
    @user = Challenge.new(params[:user])

    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
        format.html { render action: "new" }

I removed some code(like json response) for simplicity and to better illustrate the point. Notice that the params hash gets form information as inputted by a user in our application and creates a new user, based on that input. Now, our form can just have a name field for the user to fill in, but that does not mean that a malicious user cannot pass more information to the model. For example, if our User model contained the fields ‘username, password, role’, we would never want a user to set their role as admin, just by registering to our application.

This is a scenario that can happen here. It’s pretty trivial for an attacker to craft a malicious http query(either using curl, or tamperdata firefox plugin, choices are endless), in order to pass a user[:role]=’admin’ attribute. When @user.save gets executed, the attribute role is also saved and the attacker becomes an administrator.

attr_accessible to the rescue

What attr_accessible does is preventing this scenario. By specifying :

attr_accessible :username, :password

what you are doing is actually whitelisting all those model attributes that can be changed via a mass assignment operator. You are effectively saying to Rails : “When i execute user.save, i only want to save the username and password attributes. Do not save anything other than that”. Therefore, if the attacker tries to exploit the role field using mass assignment, Rails will just ignore the setter and just set all the other values, as listed in attr_accessible.

There is also an attr_protected method, which is pretty much the negation of attr_accessible. It specified which fields you want to protect. You can there specify :

 attr_protected :role

and protect the role attribute for any mass assignments. However, i highly discourage you from using attr_protected, because it’s very easy to forget to blacklist an attribute. It’s safer to use attr_accessible and deny all assignments by default, while whitelisting the secure attributes. Firewall policies creators should acknowledge that in like 90% of cases. So, from now on, never forget to use attr_accessible in ALL your models. It’s not just a good practice, it’s an essential one.