Mass Assignment Rails Params Array

Controller and Parameters 2018




bogotobogo.com site search:






Parameters




Most likely, we want to access data sent in by the user or other parameters in our controller actions.

There are two kinds of parameters possible in a web application:

  1. The first are parameters that are sent as part of the URL, called query string parameters. The query string is everything after "?" in the URL.
  2. The second type of parameter is usually referred to as POST data. This information usually comes from an HTML form which has been filled in by the user. It's called POST data because it can only be sent as part of an HTTP POST request.

Rails does not make any distinction between query string parameters and POST parameters, and both are available in the params hash in our controller:

class UsersController < ApplicationController # This action uses query string parameters because it gets run # by an HTTP GET request, but this does not make any difference # to the way in which the parameters are accessed. The URL for # this action would look like this in order to list activated # users: /users?status=activated def index if params[:status] == "activated" @users = User.activated else @users = User.inactivated end end # This action uses POST parameters. They are most likely coming # from an HTML form which the user has submitted. The URL for # this RESTful request will be "/users", and the data will be # sent as part of the request body. def create @user = User.new(params[:user]) if @user.save redirect_to @user else # This line overrides the default rendering behavior, which # would have been to render the "create" view. render "new" end end end




'params' hash

The params hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append an empty pair of square brackets "[]" to the key name:

GET /users?ids[]=1&ids;[]=2&ids;[]=3

The value of params[:ids] will now be ["1", "2", "3"]. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type.

To send a hash you include the key name inside the brackets:

<form accept-charset="UTF-8" action="/users" method="post"> <input type="text" name="user[name]" value="Auction" /> <input type="text" name="user[phone]" value="201-867-5309" /> <input type="text" name="user[address][postcode]" value="07001" /> <input type="text" name="user[address][city]" value="New Jersey" /> </form>

When this form is submitted, the value of params[:user] will be { "name" => "Auction", "phone" => "201-867-5309", "address" => { "postcode" => "07001", "city" => "New Jersey" } }.

$ rails g controller users



JSON parameters

If we're writing a web service application, we might be more comfortable accepting parameters in JSON format. If the "Content-Type" header of our request is set to "application/json", Rails will automatically convert our parameters into the params hash, which we can access as we would normally.

So for example, if we're sending this JSON content:

{ "company": { "name": "Auction", "address": "780 New Jersey" } }

We'll get:

params[:company] as { "name" => "Auction", "address" => "780 New Jersey" }

Also, if we've turned on config.wrap_parameters in our initializer or calling wrap_parameters in our controller, we can safely omit the root element in the JSON parameter. The parameters will be cloned and wrapped in the key according to our controller's name by default.

So the above parameter can be written as:

{ "name": "Auction", "address": "780 New Jersey" }

And assume that we're sending the data to CompaniesController, it would then be wrapped in :company key like this:

{ name: "Auction", address: "780 New Jersey", company: { name: "Auction", address: "780 New Jersey" } }



Routing parameters

The params hash will always contain the :controller and :action keys. But we should use the methods controller_name and action_name instead to access these values.

Any other parameters defined by the routing, such as :id will also be available.

As an example, consider a listing of users where the list can show either active or inactive users. We can add a route which captures the :status parameter in a "pretty" URL:

get '/users/:status' => 'users#index', foo: 'bar'

In this case, when a user opens the URL /users/active, params[:status] will be set to "active". When this route is used, params[:foo] will also be set to "bar" just like it was passed in the query string. In the same way params[:action] will contain "index".





Strong parameters

Sometime we get "ActiveModel::ForbiddenAttributesError" error.

It's one of the Rails security features.

The one we got is called strong parameters, which requires us to tell Rails exactly which parameters are allowed into our controller actions. This is to protect us from "mass assignment" though the ability to grab and automatically assign all controller parameters to our model in one shot makes our job easier.

Here is the same code from Getting Started with Rails that triggers the error:

class ArticlesController < ApplicationController def new end def create @article = Article.new(params[:article]) @article.save redirect_to @article end end

We have to whitelist our controller parameters to prevent wrongful mass assignment.

In this case, we want to both allow and require the title and text parameters for valid use of create. The syntax for this introduces require and permit.

The change will involve one line in the create action:

class ArticlesController < ApplicationController def new end def create @article = Article.new(params.require(:article).permit(:title, :text)) @article.save redirect_to @article end end

This is often factored out into its own method so it can be reused by multiple actions in the same controller, for example create and update.

We often put a method into private section so that we want to make sure it can't be called outside its intended context. Here is how we do it:

class ArticlesController < ApplicationController def new end def create @article = Article.new(article_params) @article.save redirect_to @article end private def article_params params.require(:article).permit(:title, :text) end end



Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization


Sponsor Open Source development activities and free contents for everyone.

Thank you.

- K Hong



Beware the Mass Assignment

Mass assignment vulnerabilities can be a hard-to-find issue in your applications. Learn how to prevent them.
Published 2013-02-04    #vulnerability#massassignment#model

There's been a lot of talk lately about issues with the Ruby on Rails framework and some of the security issues that have come to light. There's been issues with a "magic" security value and YAML parsing that allows for code injection. What I want to focus on, however, is something that came up a while back but that PHP (code built with it, not the language itself) could be vulnerable to - a mass assignment vulnerability.

Since Rails is the framework for the Ruby world, the impact of the issue was a bit larger than it would be in the PHP community. That doesn't make it any less of a threat, though. It can also be one of those difficult to track down problems as it relies on the contents of a POST request, something usually not logged by most web servers. This issue is, like most PHP vulnerabilities unfortunately, due to bad coding practices and not handling the user inputted data correctly.

The good news is that, unless you're using something like an ORM or a model layer, you might not have to worry about this issue. It's good to keep in mind, though, given the prevalence of PHP frameworks on the web.

The Problem in PHP

So, what's this "mass assignment" problem really all about? Well, it's probably easiest to illustrate with a code example:

If you've got a keen eye, you've probably already spotted the issue with the code above. Still looking? Well, here's a hint: a little filtering could take care of the problem. Still stuck? Okay, so here's the issue - notice how there's a property on our Model above called "admin". Well, in our method we check to see if the user has this value set to .

This is all well and good, but because of how the values are assigned through the method, this could lead to dangerous consequences. So, to set the stage a bit, say that you have a form that allows for the creation of a new user (a Registration form maybe). Obviously, you're not going to have a field for in the form as you don't want to make it too easy for someone to make themselves an admin. That's the problem, though - there's no filtering on the input from the user (at least in this code) that would prevent them from submitting a value for the parameter and having that pass through to the model and get set along with the rest of the data.

This is where the crux of the "mass assignment" vulnerability lies. Due to improper filtering of the input (or access control to properties) a malicious visitor to your site could include all sorts of values in the POST request to your site, hoping to score a direct hit on the right one. This can lead to one of the OWASP Top 10, A3: Broken Authentication and Session Management and can be quite dangerous for your application and its users.

A quick word of caution for those using a framework out there that includes it's own model functionality (usually just the full-stack ones) - be sure to check and be sure that you're not leaving yourself open to this kind of attack and not even knowing it. Be sure to review the code for your framework of choice (or library) before you fully trust it.

Resolution

So, we've looked at the issue - lets take a look at some of the things you can do to help mitigate the problem. Most of these are pretty simple, but some depend on the structure of your application, so keep that in mind:

  1. Filter, Filter, Filter: Yes, this has been said numerous times in other articles on this site, but this might be a little different kind of filtering than you're thinking. When you think of filtering and security, you're thinking about removing the harmful things from input that could cause problems. In this case, you want to filter out the data itself. When a user submits their POST request with the array of data, force a removal of any of the "restricted" values you don't want them to be able to override. For example, if your key is , then you'd want to call an unset on that value before calling .

  2. Enforce "restricted properties" in the Model: This is something that not a lot of frameworks or domain model libraries out there have as a feature, but it's not overly difficult to implement. Basically, what you want is a set of "restricted" items that can't be overwritten when calling something like to load the data. In our case, fits the bill. You could have a class variable that contained the list and use an in_array check to see if it's there. If it is, bypass the setting of that value.

  3. Don't make it a property: This is the one that depends on the architecture of your application. The idea here is to create your application so that things like administrative rights aren't controlled by a single property on the object. If you use something like a permission set or some other kind of flag (maybe a record in another table) it makes this kind of attack a lot less plausible. Then you could have isAdmin checks on your user reach out to this other data and evaluate from there.

Obviously, these are just a few solutions to the problem - chances are yours will differ based on how your application is structured, but this gives you a place to start.

Don't forget the type

One other thing to keep in mind that's at play here and could be tricky if you're not looking for it - see that method in the first code example? Take a look at how it's evaluating to see if the user is an admin. If you've been dealing with PHP for any length of time, you've probably come across the difference between the "equals" and how they behave. Here's a quick overview:

OperatorNameUse
=Single EqualsAssign one value to another
==Double EqualsEvaluate if two values are equal
===Triple EqualsEvaluate if two values are equal and are same type

Looking at this table, do you see the problem with the first example? (hint: it's been fixed in the second code example). Since the double equals checks to see if the values are the same but does not check type, it leaves the validation open to potential abuse. PHP is a weakly typed language and allows for the shifting of one variable type to another. Without type checking, you get interesting things like , or the fun one . Unless you include that extra "equals" in there, your check is not valid and could cause pain for you down the line.

As a general rule, evaluation in PHP should be as specific as possible. Use triple equals and the ctype_* methods to ensure your data is what you're expecting. When in doubt, filter it out (don't try to adjust).

About PHP Frameworks

I went back and looked through the model/ORM layers of some of the major frameworks in use today to see if they allowed the concept of "protected properties" in their code, but found almost nothing about it. Most of the frameworks (including CakePHP and FuelPHP). I was, however, happy to see that the upcoming version of the Laravel Framework (version 4) has something included to deal with the mass assignment issue via a and property lists:

The property names are a little odd, but they get the point across. It's good to see some concern for this in the PHP community. Hopefully some of the other frameworks with their own ORMs will follow suit.

Resources

by Chris Cornutt

With over 12 years of experience in development and a focus on application security Chris is on a quest to bring his knowledge to the masses, making application security accessible to everyone. He also is an avodcate for security in the PHP community and provides application security training and consulting services.




One thought on “Mass Assignment Rails Params Array

Leave a Reply

Your email address will not be published. Required fields are marked *