If you’ve ever been a web developer before, you’ve probably installed and used the developer toolbar, an add-on for firefox. That add-on can be both helpful and convenient. However, that same tool can and does make exploiting vulnerable web applications extremely easy. The vulnerability I’m going to go over below can appear in some non-rails apps, but is inherently present in Model View Controller (MVC) frameworks.
The exploit lies in lazy programming, as most exploits do. Improper use of update_attributes and new are the main culprits in this case, but inattention to details is just as much of a problem.
The use of the developer toolbar’s feature “edit HTML” allows anyone to modify the HTML on the page quite easily, passing bogus name-value pairs in a form or modifying existing required ones. In most apps this probably isn’t a huge issue, as the values passed through the form are explicitly set on the back-end, one by one. However, in a rails application, the use of update_attributes() and new() are highly encouraged as ways to assign all of the form values in one swoop. This can be very convenient, as most web programmers know that dealing with each value from a form can lead to a lot of code and a huge mess. Rails attempts to solve this problem by handling validation in models and keeping actions fairly clean. Everything works well until lazy programming kicks in.
Flags on a model which should only be available to admins or not available at all need to be protected. That’s why rails has a built in method for this: attr_protected. Apply this in your model on the attributes you would like to only be updated explicitly and you will have yourself a semi-safeguard against this attack. So, if an extra key-value pair is passed (say “{admin => 1}”), as long as you have attr_protected applied to that column (”admin”) rails will just ignore that column when using update_attributes.
This solution works well if you have a system where there is only 1 level of permissions and nobody else needs to be able to update that column. However, most systems have some sort of admin account which should be capable of modifying any data in the system. In this case, update_attributes could be used in conjunction with a following direct setting of the attribute if the current user has permission, but that would muddy the code up.
Bad:
def update
@blog_post = BlogPost.find(params[:id])
if @current_user.is_admin?
@blog_post.poster_id = params[:blog_post][:poster_id]
@blog_post.is_active = params[:blog_post][:is_active]
end
@blog_post.update_attributes(params[:blog_post])
end
A better solution would involve using another method in place of update_attributes which could still take a hash in order to modify attributes, but could also be passed a boolean value which indicates whether the protected attributes should be protected or not. This allows the action code to remain the same size (assuming you have a method on your account model that resembles “.is_admin?”) while selectively allowing protected values to be set.
A similar solution may need to be applied to the new() and create() methods if you use the format:
def create
@account = Account.create(params[:account])
end
in your controller.
The solution which we came up with at Viget was a patch to ActiveRecord::Base. Code below written by Ben Scofield:
module AdminUpdateAttributes
def admin_update_attributes(allowed, params)
if allowed
params.each do |k, v|
self.send("#{k.to_s}=", v)
end
self.save
else
self.update_attributes(params)
end
end
end
ActiveRecord::Base.class_eval do
include AdminUpdateAttributes
end
This solution allows us to write tiny actions in our controllers while handling admin permissions for updating protected attributes.
Good:
def update
@blog_post = BlogPost.find(params[:id])
@blog_post.admin_update_attributes(@current_user.is_admin?, params[:blog_post])
end
*A similar solution may need to be applied to the new(), create(), and attributes=() methods if you use them for mass assignment.
Recent Comments