Mass Assignment Rails Create Test

Ruby on Rails version 5 is just around the corner, with 5.0.0.beta3 released in late February. Is your app ready? Follow these steps/best practices to update your app to the latest version of Rails as smoothly as possible.

Check your Ruby version

The first thing you’ll need to know is the Ruby version your app currently supports. If you’re on an old version, then you may need to upgrade Ruby before you upgrade Rails. Rails 5 requires Ruby 2.2.2 or newer. We recommend upgrading to 2.3.0, which is the latest version of Ruby.

Check your Rails version

The current stable version of Ruby on Rails is Rails 4.2.5. If you’re on an earlier version, you should upgrade to 4.2.5 before attempting to upgrade to version 5.0.0. To avoid some serious headaches, it’s best to upgrade in increments; for example, from 4.1.5 to 4.1.15, 4.1.15 to 4.2.5 and finally from 4.2.5 to 5.0.0. Otherwise you will lose valuable deprecation warnings and be stumped when things just stop working for no apparent reason.

Use your test suite

A solid automated test suite is invaluable when performing an upgrade; tests ensure that your app executes the same way after the upgrade as it did before. If the test coverage (the amount of code that gets executed when your tests run) for your app is high, then many bugs and inconsistencies created by the upgrade process will be discovered up front rather than waiting for your users to encounter them (if you don’t write tests, make sure you use Honeybadger :)).

PROTIP: Write some tests before an upgrade if you don’t have a test suite, or if your test coverage is low.

Review your dependencies

Many surprises may be lurking in your Gemfile. When you go to update the gem, for instance, you may realize that the gem must also be updated. But wait, the latest version of RSpec has new syntax, and before long you find yourself rewriting your test suite. That’s what we call shaving a yak, and we prefer not to do it; or at least we like to know about it ahead of time. Take a look at your gems before starting the upgrade and see if you can identify some yaks; your future (less surprised) self will thank you.

Pick your battles

Speaking of yak shaving, it’s easy to get tangled up in making changes to your application code when Rails changes a core feature. Avoid changing your code as much as possible until after you’re successfully running the new version of Rails. This will reduce the number of bugs you have to deal with immediately. In some cases Rails makes this easy by providing backwards-compatibility for a deprecated feature via adding a gem.

A great example of this is strong parameters. Prior to version 4, Rails had a feature called “protected attributes” which protected model attributes from mass assignment exploits. In version 4 this feature was replaced by a new approach called “strong parameters”. Upgrading from protected attributes to strong parameters is a project in itself; one which can involve changing a lot of model, controller and test code, and in turn makes it easier to introduce new bugs.

The good news is that the Rails team released a gem which allows you to continue to use protected attributes on Rails 4, without migrating to strong parameters immediately. Using this gem means changing a single line of code vs. potentially hundreds. After you’ve completed the upgrade, plan a followup project to migrate to new features provided by your new Rails version.

Preserve history

You’ll still be making a lot of random changes to your app during the upgrade process as you update syntax and refactor deprecated code; it’s super easy to get in the zone, get things working/tests passing/whatever and then save the changes in a massive commit. Avoid doing this.

Instead, try to make small, isolated changes and commit them as you work; clearly explain your thinking in your commit messages so that you’ll remember your reasoning when you (and/or your team members) look back on the upgrade 6 months or a year from now.

Follow the instructions

The Rails team has provided an excellent Guide for Upgrading Ruby on Rails that we would be hard-pressed to beat. Follow the official instructions, and be sure to read the General Advice section for even more great tips.

Now it’s time to get to work. If you would like some help upgrading your Rails app (or if you would rather ship features instead), be sure to check out UpgradeRails.com, our concierge Rails upgrade service. We’ll manage the process of upgrading to the latest version of Rails from start to finish and also schedule regular upgrades as they are released.

In any case, let us know how it goes! Feel free to email us your advice, questions, or feedback.

Further reading

Joshua Wood

Josh is the founder of Hint and cofounder of Honeybadger.io. He's interested in a thousand different things, not the least of which are software engineering and product development.

Last revision (mm/dd/yy): 12/21/2016

Introduction

Definition

Software frameworks sometime allow developers to automatically bind HTTP request parameters into program code variables or objects to make using that framework easier on developers. This can sometimes cause harm. Attackers can sometimes use this methodology to create new parameters that the developer never intended which in turn creates or overwrites new variable or objects in program code that was not intended. This is called a mass assignment vulnerability.

Alternative Names

Depending on the language/framework in question, this vulnerability can have several alternative names

  • Mass Assignment: Ruby on Rails, NodeJS
  • Autobinding: Spring MVC, ASP.NET MVC
  • Object injection: PHP

Example

Suppose there is a form for editing a user's account information:

<form> <input name=userid type=text> <input name=password type=text> <input name=email text=text> <input type=submit> </form>

Here is the object that the form is binding to:

public class User { private String userid; private String password; private String email; private boolean isAdmin; //Getters & Setters }

Here is the controller handling the request:

@RequestMapping(value = "/addUser", method = RequestMethod.POST) public String submit(User user) { userService.add(user); return "successPage"; }

Here is the typical request:

POST /addUser userid=bobbytables&password=hashedpass&email=bobby@tables.com

And here is the exploit:

POST /addUser userid=bobbytables&password=hashedpass&email=bobby@tables.com&isAdmin=true

Exploitability

This functionality becomes exploitable when:

  • Attacker can guess common sensitive fields
  • Attacker has access to source code and can review the models for sensitive fields
  • AND the object with sensitive fields has an empty constructor

Case Studies

GitHub

In 2012, GitHub was hacked using mass assignment. A user was able to upload his public key to any organization and thus make any subsequent changes in their repositories. GitHub's Blog Post

Solutions

  • Whitelist the bindable, non-sensitive fields
  • Blacklist the non-bindable, sensitive fields
  • Use Data Transfer Objects (DTOs)

General Solutions

Data Transfer Objects (DTOs)

An architectural approach is to create Data Transfer Objects and avoid binding input directly to domain objects. Only the fields that are meant to be editable by the user are included in the DTO.

public class UserRegistrationFormDTO { private String userid; private String password; private String email; //NOTE: isAdmin field is not present //Getters & Setters }

Language & Framework Specific Solutions

Spring MVC

Whitelisting

@Controller public class UserController { @InitBinder public void initBinder(WebDataBinder binder, WebRequest request) { binder.setAllowedFields(["userid","password","email"]); } ... }

Reference

Blacklisting

@Controller public class UserController { @InitBinder public void initBinder(WebDataBinder binder, WebRequest request) { binder.setDisallowedFields(["isAdmin"]); } ... }

Reference

NodeJS + Mongoose

Whitelisting

var UserSchema = new mongoose.Schema({ userid  : String, password  : String, email  : String, isAdmin  : Boolean, }); UserSchema.statics = { User.userCreateSafeFields: ['userid', 'password', 'email'] }; var User = mongoose.model('User', UserSchema); _ = require('underscore'); var user = new User(_.pick(req.body, User.userCreateSafeFields));

ReferenceReference

Blacklisting

var massAssign = require('mongoose-mass-assign'); var UserSchema = new mongoose.Schema({ userid  : String, password  : String, email  : String, isAdmin  : { type: Boolean, protect: true, default: false } }); UserSchema.plugin(massAssign); var User = mongoose.model('User', UserSchema); /** Static method, useful for creation **/ var user = User.massAssign(req.body); /** Instance method, useful for updating **/ var user = new User; user.massAssign(req.body); /** Static massUpdate method **/ var input = { userid: 'bhelx', isAdmin: 'true' }; User.update({ '_id': someId }, { $set: User.massUpdate(input) }, console.log);

Reference

Ruby On Rails

Reference

Django

Reference

ASP.NET

Reference

PHP Laravel + Eloquent

Whitelisting

<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { private $userid; private $password; private $email; private $isAdmin; protected $fillable = array('userid','password','email'); }

Reference

Blacklisting

<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { private $userid; private $password; private $email; private $isAdmin; protected $guarded = array('isAdmin'); }

Reference

Grails

Reference

Play

Reference

Jackson (JSON Object Mapper)

ReferenceReference

GSON (JSON Object Mapper)

ReferenceReference

JSON-Lib (JSON Object Mapper)

Reference

Flexjson (JSON Object Mapper)

Reference

Authors and Primary Editors

References and future reading

Other Cheatsheets

Comments

Leave a Reply

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