ActiveRecord Vulnerability - Circumvention of attr_protected
groups.google.com
groups.google.com
Finding people like Ryan to work with--- "sleepers" who don't have long track records of filing public vulnerabilities, but who are secretly terrifying killing machines --- is the single best thing about my job.
Ryan found us on HN after beating the Stripe CTF. If you're like Ryan, ping me; I will go out of my way to make sure we tell you everything we can about why you should work with us.
Back on topic: if I was going to place a bet on something about Rails security, it'd be that there are more regex vulnerabilities in the tree. I am uncomfortable with how much Rails leans on regex for policy decisions.
Finally, said this before, saying it again: if you have a Rails app with customers, you need to be following @joernchen on Twitter full stop.
At the same time it's slightly shocking that no-one has greped Rails for this kind of well-known vulnerability before, never mind auditing for less obvious ones.
https://groups.google.com/forum/?fromgroups=#!topic/rubyonra... (Rails 2.3 and 3.0)
https://groups.google.com/forum/?fromgroups=#!topic/rubyonra... (JSON library)
And in a short followup:
"To be clear, updating Rails doesn't necessarily mean the JSON gem will be updated. Please ensure that you are running JSON version 1.7.7, 1.6.8, or 1.5.5. You can do this by adding the dependency to your Gemfile." -- Aaron Patterson
https://groups.google.com/forum/#!forum/rubyonrails-security
- @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
+ @regex = /\A(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})\z/
^ and $ only match the first line in ruby, whereas \A and \z match across all lines.Why not \Z, you ask? \Z will match to the end of the string but ignore a trailing newline.
irb(main):001:0> !!("hello\n" =~ /\Ahello\z/)
=> false
irb(main):002:0> !!("hello\n" =~ /\Ahello\Z/)
=> trueI say that about 95% of the bugs I ever see...especially ones written by me.
In general, you may be better off avoiding regexes when you can, especially if it's security-sensitive. They're very useful, but they're very easy to get wrong, especially when they get complex. This case, for instance, looks like it would have been impossible if they checked if the attribute were in a list, instead of building a regex. It might be faster with a regex in this case, but for most people that's a (massively) premature optimization for (imperceptibly) small gain.
How do you then get rails to assign it to the correct (blacklisted) identifier?
It's certainly possible. Maybe, maybe not.
The recent spate of Rails vulnerabilities - the really scary ones at least - all stemmed from the same root cause: folks were a little too lenient with how they handled YAML parsing.
Once that was discovered, a lot more attention has been directed to how Rails handles different kinds of parsing.
It's possible! that other frameworks have had similar cascading mistakes, but we won't know until more code reviews occur. Maybe in this particular case Rails-core was especially lenient, but (as far as I remember) dedicated security people have only taken a keener interest in the past year or so.
If you are using PyYaml.Loader instead of PyYaml.SafeLoader for anything coming from a user, you are at risk of this problem.
http://pyyaml.org/wiki/PyYAMLDocumentation#YAMLtagsandPython...
I cannot remember the last time I came across a php project that did something similar.
Beyond that, your guess is as good as mine. I'm sure that /someone/ has been looking at Django at least to see if there are similar issues.
That is not to say Django doesn't have issues; it undoubtably does. I just think the hidden surface area is smaller.
With regard to this vulnerability, however, the '^' and '$' regex pattern characters in python match the beginning and end (or end + '\n') of the string by default. Multiline mode has to be enabled explicitly:
import re
re.match(r'^test$', 'test\n multiline') == None
re.match(r'^test$', 'test\n multiline', re.MULTILINE) != None
So, I think it's a little less likely that this particular vulnerability would be an issue. It's still possible for someone to leave off the '$', but at least that case is a little more obvious.
Also, the Django codebase doesn't have any param processing code that uses whitelisting/blacklisting like this; you have to explicitly lookup values in request.GET and request.POST or use specific field names in a Form. It's a little less convenient compared to mass assignment, but more secure by default.
class SomeForm(ModelForm):
class Meta:
model = SomeModel
fields = [ whitelist ]
exclude = [ blacklist ]
Both fields and exclude are optional, if neither are specified 'all'[2] fields for the model will be included in the form.[1] https://docs.djangoproject.com/en/1.4/topics/forms/modelform...
[2] The model can blacklist certain fields with editable=False in the field definition as well, which afaik trumps anything a ModelForm does.
(we tend to keep an eye out for issues affecting other frameworks/libraries, both to coordinate and to check our own stuff -- security is really damned hard, and the thing to do is watch and learn rather than point and laugh)
It's not out yet, but soon: http://gemcanary.com
If i have a table with 20 columns, 19 of which i want accessible (lets exclude a private UK). I also expect the schema for the table to be volatile. Why should i even consider while listing 19+ over blacklisting 1?
So no, I'd rather not start whitelisting my models.
Just like when rails people said they had a vulnerability in action dispatch but it was actually a YAML vulnerability. (both used in a lot of non-rails projects)
So please don't overlook this issue even if you are not using rails.
Right from the Github mass-assignment [1] vulnerability to the recent YAML & JSON parsing vulnerabilities, it's the same core concept being violated.
This gets me thinking -- is Rails the right choice for a large project with JSON, XML, & regular HTML endpoints?
PS: I'm not sure what the code-review policy for Rails is, but now would be the time to call-out people who wrote this bad code and NOT auto-merge their future commits without at least two peer reviews.
Now is a good time to patch your code and keep building your company.
Every framework has security bugs.
Jumping ship to a framework you don't understand, possibly one that is harder to update, is a knee-jerk reactionary response to the problem.
If all these compromises worry you, invest some time in setting a HIDS (Host Intrusion Detection System), subscribing to the relevant security mailing lists, and ensuring that your deployment workflow allows you to patch production code within a few minutes.
https://rubyonrails-security.googlegroups.com/attach/bb44b98...
https://ariejan.net/2009/10/26/how-to-create-and-apply-a-pat...
gem 'rails', '3.0.20'
to gem 'rails', github: 'rails/rails', branch: '3-0-stable'In this case, apparently it was possible to 'hide' your attribute behind a newline, making it invisible to the attr_protected code, but somehow the attribute could still be valid (for no reason rails calls #strip on it or something?).
So conclusion: this doesn't lead to mass assignment. only DoS.
[29] pry(main)> x.update_attributes("client_\nsecret"=>1) (0.1ms) begin transaction (0.1ms) rollback transaction ActiveRecord::UnknownAttributeError: unknown attribute: client_ secret
But DEPRECATION WARNING: The method `sdf client_secret=', matching the attribute `client_secret' has dispatched through method_missing. This shouldn't happen, because `client_secret' is a column of the table. If this error has happened through normal usage of Active Record (rather than through your own code or external libraries), please report it as a bug. (called from block in assign_attributes at /Users/homakov/.rvm/gems/ruby-1.9.3-p194/bundler/gems/protected_attributes-369818eedeaa/lib/active_record/mass_assignment_security/attribute_assignment.rb:67)
So it's hidden in method_missing!
The spate of recent vulnerabilities has more to do with YAML in particular than with rails itself.
These are not new vulnerabilities, but are being discovered now, which means more spotlights are shining on the project. That's a sign of maturity too. From a maintenance standpoint, it will be a pain to apply patches to older software and those seeing heavy use, but it will cause some reexamination of existing code and practices.
There will be a domino effect of more eyes focusing on the code now, which in turn will lead to more discoveries of course, but hopefully, new fixes too.
I suggest that you follow the advice, "if you can't think of anything nice or constructive to say, bite your tongue". The irony is not lost on me that in taking you to task for the tone you couch your comments in I am failing to live up to my own advice, but I'll make an exception here.
The thought even crossed my mind while carefully drafting the above comment that I ought to preempt the accusation that I was committing this logical fallacy but I decided not to and now I wish the opposite.
Anyway, I hope you see the difference?
So it's basically a training system to encourage people to comment like this (remember; you need 500+ karma to down vote, so for many people, you can only ignore or up vote; therefore, all you see for a post is upvotes).
:/