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.
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" %>
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.
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.
I agree with @dominiquebrezinski that storing an encrypted version of the allowed fields on the client is probably not a good idea. It reminds me of ASP.NET where application state was also stored on the client. Practical Padding Oracle Attacks, developed based on a theory by Vaudenay completely devastated the security there.
Within the application, we know as a fact exactly what fields are allowed. Sending this information on a round trip to the client and back would only weaken what was already established as a hard fact. On an abstract level, cryptography would be used trying to prove something to ourselves that we already knew without it in the first place.
Note that this is very different from signed authentication tokens - there it's actually the user trying to prove something (their identity) to us (the application). In the "encrypted/signed fields" case it's just us trying to prove something to ourselves. In addition, the crypto would put more computational effort on the server and more data would be sent over the wire for each request/response, state would have to be tracked and, as we all know, it's very easy to make mistakes. For example when dealing with MACs, it can be very easy to make yourself vulnerable to timing attacks. If the fields are only encrypted and neither MACed nor signed, then we're basically screwed due to the malleability of ciphertexts. Then there's replay attacks and whatnot else.
But even if everything is done right, exposing ciphertexts like this to the client offers the possibility for brute-forcing your way in. There is no restriction on how many ciphertexts an attacker could obtain from the application - they would behave like ordinary users. A really sophisticated solution that would exploit volume and parallelism would certainly have a chance to brute-force keys within an acceptable time frame.