Ruby on Rails vulnerable to mass assignment and SQL injection
Here’s the gist: The default JSON parser can be used to inject malicious objects into the params hash of a Rails application. This allows for tampering with
ActiveRecord::Base functionality like dynamic finders and attribute assignment, eventually leading to mass assignment of blacklisted attributes or even SQL injection.
Besides deserializing simple data types, the default JSON parser used by Rails supports deserialization of more complex classes. When parsing a JSON string, the parser honors a special key called
json_class. It will try to instantiate this class by calling
.json_create on it. Consider the following example:
This mechanism can be used to deserialize JSON into instances of more complex Ruby classes. This isn’t a problem in itself, as nearly no class provided by the standard Rails stack implements a
.json_create method, and thus no Rails internal class can be instantiated this way (as opposed to the YAML parser vulnerability, which allowed to instantiate every class). But unfortunately,
json >= 1.6.7 ships with a class called
JSON::GenericObject which is basically an
OpenStruct descendant class:
As you can see above, this class can be used to ‘fake’ method return values: If I’d expect to work on a
String somewhere in my code but actually work on an
JSON::GenericObject instance, methods like
#strip don’t behave as assumed. An attacker can use this behavior to trick Rails into accepting values for mass assignment protected attributes. This works by passing a nested data structure consisting of
JSON::GenericObject instances to a model’s initializer. Take a look at an example from my Blog-in-10-minutes app:
As you can see, I’ve created a
Post instance and directly assigned a protected attribute — that’s bad. Rails’ mass assignment protection errorneously thinks it always works on simple data types like strings and hashes. It tries to convert the supplied hash keys to strings by calling
#stringify_keys on the hash. Afterwards it rejects each attribute that is on the mass assignment blacklist by calling
#reject on the hash. If I pass the above data structure to
Post.create, all those method calls are actually handled by my
JSON::GenericObject, and Rails thinks it deals with a sanitized hash even though I can freely determine the value of all attributes.
This mechanism works at least for ActiveRecord and Mongoid, other ORMs untested. ActiveRecord is even vulnerable to SQL injection: The abstract database adapter’s ID quoting can be circumvented by providing “pre-quoted” arguments:
I was able to verify this vulnerability remotely against a Rails 3.2.11 app consisting of a simple scaffold. Due to the severity of this issue, all Rails applications should be updated to use the latest stable release of json immediately. Your application is at risk!
Thanks to Michael Koziarski from Rails’ security team and Aaron Patterson for the constructive collaboration on this issue.