Skip to content

Instantly share code, notes, and snippets.

@wycats
Created March 4, 2012 18:06
Show Gist options
  • Save wycats/1974187 to your computer and use it in GitHub Desktop.
Save wycats/1974187 to your computer and use it in GitHub Desktop.

Proposal for Improving Mass Assignment

For a while, I have felt that the following is the correct way to improve the mass assignment problem without increasing the burden on new users. Now that the problem with the Rails default has been brought up again, it's a good time to revisit it.

Sign Allowed Fields

When creating a form with form_for, include a signed token including all of the fields that were created at form creation time. Only these fields are allowed.

To allow new known fields to be added via JS, we could add:

<%= f.allowed_fields "foo", "bar", "baz" %>

Mark accessible fields in the controller

The first strategy will not full satisfy apps that support a lot of HTTP requests that do not come from forms generated by Rails.

Because accessible fields is usually a function of authorization, and is not global, we should move allowed fields into the controller. The basic idea is:

class PostsController < ApplicationController
  # attributes can be marked accessible for the entire controller
  attr_accessible :foo, :bar

  def create
    # mass assignment can also be done on an instance basis
    # this can be used to override the class defaults
    attr_accessible(true) if user.admin?
    ...
  end
end

I would imagine that Rails authorization frameworks like CanCan could add sugar to make this even easier in common cases.

Conclusion

The core problem with Rails mass assignment is that attribute protection is an authorization concern. By moving it to the controller, we can have smart defaults (like signed fields in form_for) and in more advanced cases, make it easier to decide what fields are allowed on a per-user basis.

By moving it into the correct place, we will probably find other nice abstractions that we can use over time to make nice defaults for users without compromising security.

@tqbf
Copy link

tqbf commented Apr 6, 2012

The particular encryptor used in this suggestion does appear to be unverified and susceptible to chosen plaintext attacks. Not only that, but it will generate an uncaught exception in OpenSSL's block cipher decrypt routine when ciphertexts are tampered with; it thus has a padding oracle.

Rails includes a well-reviewed message encryptor in ActiveSupport::MessageEncryptor; MessageEncryptor#sign_and_encrypt includes an HMAC-SHA1 signature on the ciphertext, which defeats these attacks. typing AES into your Rails app (or extension) is indeed a code smell.

@edison
Copy link

edison commented Apr 18, 2012

Agreed. Im having some trouble about this.

# Attr :name is protected in model, but in some cases
# would be easy to access by this way:
Tag.create(name: 'foo')
# Actually, I needed to do this:
tag = Tag.new
tag.name = 'foo'
tag.save

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment