#26 Hackers Love Mass Assignment
Mass assignment is something most Rails programmers make use of as it provides an easy way to populate the properties of a model object from a form. Unfortunately its simplicity can make it a target for hackers. We’ll explain how and show you how to solve it in this episode.
Our simple registration form.
Above is a simple user registration form. When a user enters their user name and clicks “submit” a new User model is created. Our user model is defined in the schema like this, with a string field called name
and a boolean field called admin
that defaults to false.
create_table :users do |t| t.string :name t.boolean :admin, :default => false, :null => false end
When a user fills in the form and clicks register, the create action is called.
def create @user = User.new(params[:user]) if @user.save flash[:notice] = "Successfully registered" redirect_to :action => ’show’, :id => @user.id else render :action => :new end end
When a user is created, the new User is populated from the params
hash which, as discussed in the previous episode, should not be trusted. We are passing all of the values in the params[:user]
hash and setting them as attributes of our new user. We’ll show now how this can be compromised by a malicious hacker to make himself an admin.
Hacking The Site
A command-line tool like curl
can be used to pass POST parameters to a page.
curl -d "user[name]=hacker&user[admin]=1" http://localhost:3000/Users/
In the line above we’ve sent a name of ‘hacker’ and set admin to true
. If we look in our development log we can see that new user has become an admin.
Processing UsersController#create (for 127.0.0.1 at 2009-02-03 20:18:54) [POST] Session ID: 8daeaad6eb382c903e595e704b626ef7 Parameters: {"user"=>{"name"=>"hacker", "admin"=>"1"}, "action"=>"create", "controller"=>"users"} SQL (0.000390) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’t’) Redirected to http://localhost:3000/users/show/2
The hacker was able to do this because of mass assignment: we’re creating a new user using the parameters supplied in the params hash and the hacker has supplied an admin parameter which has been passed through. The hacker has therefore made himself an admin.
Protecting Attributes
The way to stop this kind of attack is to limit the attributes that can be set from the form (or from a POST method). We do this in the model, in this case our User model. Rails has a method called attr_protected
which allows you to define which of a method’s attributes can’t be set via mass assignment.
class User < ActiveRecord::Base has_many :comments attr_protected :admin end
Our User model with a protected admin attribute.
Now, if we send our curl
command again and look in the development log then we can see that the admin attribute hasn’t been set to true, even though it was supplied as true in the params hash.
Processing UsersController#create (for 127.0.0.1 at 2009-02-03 20:37:49) [POST] Session ID: 381cee077c1367bf0cc410a2259adb96 Parameters: {"user"=>{"name"=>"hacker", "admin"=>"1"}, "action"=>"create", "controller"=>"users"} SQL (0.000327) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’f’) Redirected to http://localhost:3000/users/show/5
The admin attribute is now set from the params hash.
Not Quite There Yet
We’ve now stopped that method of hacking, but there’s still a hole in our site. There is a relationship in our application so that a user has many comments and has_many provides a way of setting the comment_ids via mass assignment. We use curl
again to hack the comment ids.
curl -d "user[name]=hacker&user[admin]=1&user[comment_ids][]=1&user[comment_ids]=2" http://localhost:3000/users/create
Gaining control of comments that aren’t ours.
The has_many
relationship between User
and Comment
gives the User model a comment_ids
method that takes an array of comment ids. We’ve hacked the parameters hash above to claim ownership of the comments with ids 1 and 2. In the development log we can see that our hacker now owns those comments.
Processing UsersController#create (for 127.0.0.1 at 2009-02-04 20:27:36) [POST] Session ID: e6bee21260899c7dce47bc5040dcd467 Parameters: {"user"=>{"name"=>"hacker", "comment_ids"=>["1", "2"], "admin"=>"1"}, "action"=>"create", "controller"=>"users"} Comment Load (0.001) SELECT * FROM comments WHERE (comments."id" IN (1,2)) SQL (0.001) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’f’) Comment Update (0.000094) UPDATE comments SET "title" = ’Comment 1’, "user_id" = 8 WHERE "id" = 1 Comment Update (0.000071) UPDATE comments SET "title" = ’Comment 2’, "user_id" = 8 WHERE "id" = 2
Our hacker now owns comments that aren’t his.
To stop this happening it’s better to use attr_accessible
in our model instead of attr_protected
. Now we’re explicitly saying which attributes can be modified by mass assignment. We’ll update our User model to just allow updates to the name attribute.
class User < ActiveRecord::Base has_many :comments attr_accessible :name end
A last look in the development log shows that our hacker hasn’t got admin privileges and hasn’t got ownership of any comments that aren’t his.
Processing UsersController#create (for 127.0.0.1 at 2009-02-04 20:39:15) [POST] Session ID: 48b9264e8da94d0a0edadce5e31ac500 Parameters: {"user"=>{"name"=>"hacker", "comment_ids"=>["1", "2"], "admin"=>"1"}, "action"=>"create", "controller"=>"users"} SQL (0.000307) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’f’) Redirected to http://localhost:3000/users/show/9
We’ve seen in this episode that it’s easy for unprotected models to have their attributes manipulated by malicious users. Use of attr_accessible
can stop these attacks and keep your models safe.