April 2012 Archives
Thu Apr 5 15:25:15 UTC 2012
A worst practice in Ruby on Rails
A pretty common coding pattern in Ruby on Rails is the following:
app/controller/users_controller.rb: def update @user = User.find(params[:id]) params[:user].delete(:admin) # make sure to protect admin flag respond_to do |format| if @user.update_attributes(params[:user]) [...]One would assume here that params[:user].delete(:admin) would not allow the admin flag to be assigned.
This is a bad coding practice I've seen in various RoR apps in the wild. Instead attr_protected / attr_accessible should be used.
So, how would you exploit this controller to assign the admin flag despite is being deleted?
Trick 1: Mass assignment magic to the help
Observe the following Ruyb on Rails code:
rails/activerecord/lib/active_record/attribute_assignment.rb: 01 def assign_attributes(new_attributes, options = {}) 02 return unless new_attributes 03 04 attributes = new_attributes.stringify_keys 05 multi_parameter_attributes = [] 06 nested_parameter_attributes = [] 07 @mass_assignment_options = options 08 09 unless options[:without_protection] 10 attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role) 11 end 12 13 attributes.each do |k, v| 14 if k.include?("(") 15 multi_parameter_attributes << [ k, v ] 16 elsif respond_to?("#{k}=") 17 if v.is_a?(Hash) 18 nested_parameter_attributes << [ k, v ] 19 else 20 send("#{k}=", v) 21 end 22 else 23 raise(UnknownAttributeError, "unknown attribute: #{k}") 24 end 25 end 26 27 # assign any deferred nested attributes after the base attributes have been set 28 nested_parameter_attributes.each do |k,v| 29 send("#{k}=", v) 30 end 31 32 @mass_assignment_options = nil 33 assign_multiparameter_attributes(multi_parameter_attributes) 34 endSo if the model does not use the build in mass assignment protection (attr_protected/attr_accessible),
the code flow would allow to call any public method ending in "=". In line 16 the code checks for a public
mehtod for each attribute key. Subsequently in line 29 it calls the method with the value Hash of the key as argument.
How does this help us?
See further in the same piece of ActiveRecord code:
def attributes=(new_attributes) return unless new_attributes.is_a?(Hash) assign_attributes(new_attributes) end
\o/ attributes= ends in =, and is callable via assign_attributes. So if we give the admin attribute like:
user[attributes][admin]=1 it won't be deleted by params[:user].delete(:admin) due to being params[:user][:attributes]
This issue is briefly described (without elaborating how to exploit this) here
Trick 2: Multiparameter attributes
Another way to circumvent the above deletion of the admin flag would be so called "multiparameter attributes". Those work as follows:If you want to give multiple parameters to one attribute it's encoded in Ruby on Rails like:
user[name(1)]=first_name&user[name(2)]=last_name
So here the parameter "name" gets two attributes "first_name" and "last_name".
Now we can do the following to exploit the above controller:
user[admin(1)]=true
Here we give only one parameter as a multiparameter which is completely legal, as the parameter is now "admin(1)" the user.delete(:admin) statement won't catch it. :)
Credit: @meder came up with this 2nd approach on the <spam>RailsFun mailinglist</spam>