#26 Hackers Love Mass Assignment
Your site may be at risk! When using mass assignment, you are giving the user complete control over that model and its associations. See how a hacker might use this vulnerability and learn how to stop it in this episode.
Thanks again Ryan for this screencast. That one was very interesting and the solution if so easy to setup... Nice !
Amazing! I imagine this may and actually WILL be overlooked on many rails sites.
Very helpful. I had no idea ActiveRecord relationships could be hiijacked like that. I guess it's better to use a white-list than a black-list since you never really know what attributes are available in your models.
So if it's not through mass assignment, and you need to declare someone an admin, or set someone as an owner of the comment - then would you place it manually in the controller?
@comment.user_id = current_user
What about the admin? How would that be set?
Thanks for visualizing http://www.rorsecurity.info/2007/03/20/do-not-create-records-directly-from-form-parameters/
This security series is excellent and this episode is the best of the bunch so far. I really like it that you showed how a hacker might abuse the site. I think it really helps developers see how easy it is to formulate attacks. That should me addressing security more of a concern. Well, I hope...
@Daniel,
Yes, you can set someone as the owner of the comment that way. Or better you could build the comment through the user:
@comment = current_user.comments.build(params[:comment])
If you want to set the user admin attribute you could do it like this:
@user.admin = true
Ryan,
I think this is perhaps the best screencast yet. I especially appreciated the curl examples showing what a malicious user might do. Now I'm off to add attr_accessible to my models.
I knew some hackers triks, but not this one. Really Good!!
Great screencast , I love this site!!!
Question:
Why not just verify that the metod is post like this?
verify :method => :post, :only =>[ :destroy, :create, :update ], :redirect_to => { :action => :list }
This seems more simple and this way you can not create new users using curl (or the browser address field)
Or I'm I just not getting it?? :)
Cheers
Martin,
Curl can send information via a POST request, so that wouldn't stop a malicious user. Ryan's suggestion is definitely worth looking into...
Ryan,
A bit off topic, but can you post the .css code for this episode? I'm sure it's pretty simple, but I haven't done much .csss so the more examples I can see the better.
thx
Mike
@Mike, the CSS code is very similar to the one I use in the episode about the generator script. You can find the episode and the code here:
http://railscasts.com/episodes/58
The file is in "app_layout_generator.zip".
Ryan,
Thanks for the link.
Another q, might be a curl related issue.
When I submit a new user through the web browser (I'm just using a barebones scaffolded interface), I see in the dev log 3 entries, one each for the Controller#new, #create, then #list actions.
But when I try using curl as per your example, I'm only getting an #index log entry when I pass ../users as the last URL element.
If I pass .../new as the last element, I get a #new log entry, but not a #create.
If I pass ../create I do get a #create log /controller entry, and the log shows the parameters ARE passed, but the SQL statements are inserting NULL values !
This is all before I add any of the modifications you suggest for protection.
Any ideas ?
Mike
@Mike, unlike a web browser, curl doesn't automatically follow redirects. That is why you're seeing both "create" and "index" in the log when using a web browser, but only "create" when using curl.
If you're getting null values when using curl, I'm guessing it's because the attribute names aren't correct. Make sure you're naming the attributes in curl exactly how I'm doing in this episode (model name followed by the attribute name in square brackets).
Normally you don't need to bother with curl, I just used it for demonstration purposes to show how a hacker might break into a site.
Ryan,
I got it working ;)
After getting something happening with the '/create' stuck on the end of the curl command, the problem was that I was passing 'users[name ...' instead of 'user[name...'.
That pluralization stuff again !
I figured it out by looking at the Html source for the form code and also with the help of a brief curl tutorial that had an example http://curl.haxx.se/docs/httpscripting.html
At first I thought mabye it was because the web-browser created data was passing "commit"=>"Create","action"=>"create", "controller"=>"testusers"} parameters, and the curl commands wasn't, but that wasn't the problem.
I'm still wondering how your example worked ... I see that you have a "redrecting message" popup, so I assume you put a redirect inside your users.rhtml file to create .. just for the purpose of this demo ?
Mike
P.s. I also wasted a lot of time trying to get 'rake db:migrate' to work, might I suggest you do a railscast on how it works, specifically the versioning system. I found out on the railsforum that the magic commands were "rake db:migrate VERSION=0 ... folowed by rake db:migrate".
@Mike, glad you got it working. I the redirect is done in the controller after the model saves. It isn't done in an rhtml file.
On top of restricting mass assignment with attr_accessible and attr_protected, why not do something like below, restricting access to fields on a method by method basis?
params[:user].delete_if { |key, value|
!([:company_name,:profile].include?(key))
}
user.update_attributes(params[:user])
This could be done as an application helper method that takes an array and a hash.
After typing the above though, I kind of realize filtering out attributes by method might be pointless, when really the goal is to filter by user or role. A user with access to a method might have no business running it on a particular row,. The need to check permissions for the requested row will often be there, but if your doing that, why not just create a function that checks for permissions by row and field, and filters the params hash to suit?
One could pass this params filter method an ActiveRecord object, login credentials, and the params hash - to get back a sanitized params hash without the attributes the user has no access to.
Another method might identify if a particular user has access to an array attributes for a given ActiveRecord object/row, returning true or false. AJAX apps could use this to dynamically add edit links for row/attributes where the user has permission.
Does something like this exist already? Perhaps it is just overkill, usually there are just admins and simple users, where simple users are restricted the same fields but different subsets of rows. But sometimes you do end up with with a third "power user" tier that requires less than complete access to all the fields and rows.
Getting back to the original subject of mass assignment, preceding mass assignment with this params filter would make it safe to use wherever, keeping things a litte simpler on the code end by pushing permissions related stuff into the database.
After seeing this screencast last week I created a simple plugin to search your Rails projects for this exploit and I thought some of you may find it useful. I called it audit_mass_assignment:
http://code.google.com/p/audit-mass-assignment/
rake audit:mass_assignment
The audit goes through your models and checks for attr_accessible. If the model doesn’t use it, the model fails the audit. The plugin is obviously only for people that use the whitelist approach.
I could see a whole family of “audit” plugins that could check certain aspects of Rails projects for bad/insecure patterns. You could include (or omit) any kinds of audits that you want and run them before you deploy your app with “rake audit”. Obviously not every exploit or bad pattern can be audited this way but many can be.
@Ryan
@user.admin = true
Okay, I understand that. But that still strikes my curiosity. Basically any time an attribute needs to be updated by the user, it can be hacked by a user if they know the column field, or whatever sets it.
So, how would you protect the ability for an admin to make another user an admin or something similar?
If it's in the form that means any user can use the same form and gain those privileges no?
Another example:
Let's say a user has a post, they want to make it either public or private. Since all users have access to that form, what stops them from submitting it to another user_id and another post and changing the privacy level of it? That attribute has to be accessible for it to be set a certain level of privacy.
@Daniel,
To make an attribute only settable by admins you can do this:
@user.admin = params[:user][:admin] if current_user.admin?
This way the admin parameter for the user will only be set if the current user is an admin. Does that answer your question?
this makes me want to try to hack rails sites... :-P just kidding. though it would be really cool if i could hack someone's site who i know, i'd get lots of cred for that! but honestly this was really cool because i think there's a lack of stuff addressing security concerns in rails, i've kind of wondered about things like this before but never really seen it explained... thanks
Check out my blog post on mass assignment..
http://www.snailrails.com/2008/1/mass-assignment
I have moved my audit_mass_assignment plugin to GitHub:
http://github.com/ryanlowe/audit_mass_assignment
The plugin scans the models in your project and lets you know which ones do not use the attr_accessible whitelist approach to protect attributes.
It's very handy for projects that have a lot of models, get updated sporadically or are updated by many people. An audit like this could be run as part of a continuous integration test suite.
Hi, first, thanks for all those great videos!
I'm still curious though, that means that any user would be able to create a user, only providing a name field?
This could not be more useful! I'm looking at writing some code that handle delicate data at the moment.
Your screen casts are incredibly useful, keep up the fantastic work!
Russ
Just revisting / redownloading this again.
It really should be titled "Crackers ..." .. not "Hackers ..." ;)
Mike
Ryan, you say to add
@user.admin = params[:user][:admin] if current_user.admin?
to prevent mass assignment of the admin field.
Where would this piece of code go exactly? In the update method of the controller?
Over there we already have
@user = User.find(params[:id])
..sry im new at this...
I'm a little confused. I get the 'why' and the 'how'. But not quite the 'what'. Anything in attr_accessible is hackable so why would you want anything in it?
Hi and thank you for these very good screencasts.
I wonder if an other possible way could be check if form was submitted from the same server ip as the development server.
So a curl from outside would not be possible.
Ryan (comment 41):
"Hackable" is a loaded word. In this case, we're talking about authenticated users modifying data that they shouldn't be able to modify.
If a user modifies her first name, it's not hacking, and first_name is something that would definitely go in attr_accessible. We don't care if she does it via the website, or curl, or anything else; if she's logged in, she can edit her name. So, you could say that it's okay for first_name to be "hackable".
However, if a user attempted to modify her admin status, that's definitely hacking. Even though she's logged in, she shouldn't be able to set her admin column from 0 to 1. Obviously, we wouldn't provide a way for her to do this via the website, but excluding the admin column from attr_accessible prevents her from doing it with curl or other utilities.
Make sense?
Ryan, What if you have a model where none of the attributes should be modified by the user (an e-commerce app for example where the admin only can add products)? You say that attr_protected leaves a vulnerability and should be avoided...so what is the alternative? Should I simply have a line for attr_accessible and assign no attributes to it?
Thanks for the screencast!
Update:
http://blog.remarkablelabs.com/2012/12/strong-parameters-rails-4-countdown-to-2013
This episode has been updated for Rails 5 as a blog post. Mass Assignment in Rails 5