introduction
second update
Please also see my Anatomy of an Attack Against 1.1.4, which is more detailed and more accurate regarding the mechanics of the flaw.
first update
The problem is not limited to remote execution; there are denial-of-service issues too. See this thread on Ruby-Forum, and the official explanation on the Rails weblog. However, as Chris Carter points out on Peter Krantz’s blog:
“The scary part is that it can run your schema.rb file, which will drop all your tables and recreate them.”
I always use a SQL schema instead of a Ruby schema because I use a visual database modeler, so I was immune to this particular use of the bug. Your first line of defense should be to rename schema.rb
to schema.something_else
.
A vulnerability tester (not an exploit) has been posted to Pastie. Usage instructions and some tests of different combinations of webservers and Rails versions are here, on the Caboose wiki. The testing script was written by Zed Shaw. Be very careful to check that it won’t accidentally do something dangerous to your app. Warning: don’t run it against production, because it will reload your database schema if your application is vulnerable.
details
The patch location is easily discovered with the elite hacking tool diff -r
:
base[0, extended_root.length] == extended_root || base =~ %r{rails-[\d.]+/builtin}
def file_kinds(kind)
((@file_kinds ||= []) << kind).uniq! || @file_kinds
end
file_kinds :app
base.match(/\A#{Regexp.escape(extended_root)}\/*#{file_kinds(:lib) * '|'}/) || base =~ %r{rails-[\d.]+/builtin}
It looks like, for example, that if your Rails installation is in /www/rails/
, passing a string such as /www/rails/../../tmp/
would pass the old validation, and if you had managed to upload a file such as hax_controller.rb
to /tmp/
, a route request to /hax/
would force Rails to run your arbitrary code. It seemed like the browser-submittable header field $HTTP_LOAD_PATH
would get rewritten to $LOAD_PATH
during certain requests. This may not be true in production mode, or it may depend on your webserver. Even so, file_column
and other plugins upload things to places within the Rails tree (e.g. #{RAILS_ROOT}/public/files/model_name/id/filename
), so it wouldn’t have been necessary to backtrack on the path, because /public/files/model_name/id/filename
would have been a valid route even though there was no such controller. (I think. I am not an expert on routes.) If you could predict the landing-place of an uploaded .rb
file, you could compromise the system.
However, as is mentioned in the update above, the default Rails install tree contains some files which are already dangerous and could be executed without having to manipulate the $LOAD_PATH
. This means there is a built-in potential for denials-of-service as well as database corruption if the schema is reset or if migrations are forced to run in the wrong order.
criticism
The security-through-obscurity response of the Rails team has been widely criticized. As a commenter notes on the Rails blog, this is the kind of thing that gets software permanently rejected by organizations where risk management is a high priority. Kristian Köhntopp reminded me that Theo de Raadt tried the same approach with the famous OpenSSH vulnerability. It didn’t work then either. Open-source and limited disclosure policies tend not to go well together.
In other news, I posted release 12 of the has_many_polymorphs plugin.
Thanks for the exchange, Evan. It seems that the LOAD_PATH has been accepted in 1.1.4 in a file upload, which also is fixed by the 1.1.5 patch.
thanks Evan for the explanation.
I agree with your final comments.
I would’ve never expected such obscurity from the Rails team.
I do not see what the point of this revelation is.
Is it just to gather fame?
I mean if the core team says it’s critical then that should be enough reason to upgrade.
And I do appreciate their discretion in not revealing how someone might exploit the code.
This post was done with bad taste in my view since they said they’d give full disclosure within a small amount of time.
“full disclosure within a small amount of time”
That’s an oxymoron. If the disclosure were “full”, we’d have all the information now.
The Rails core team is still a young school of fish in the ocean of open source software, so I think this heinous mistake can be overlooked as a first offense. Here’s to hoping they learn from it and don’t repeat it.
Jan: Running diff is hardly magic. If someone really wanted to exploit this, they’d know how to run diff. So basically, the crackers have the info to crack, yet developers don’t (immediately) get the information to assess risk. Evan’s post fixes this balance.
Evan: Do you speculate that if your machine is properly secured with, say, chroot, then this isn’t a problem?
Was that the only security related difference you found? I was going to check out both releases and diff them myself, but my SVN is screwed up and won’t work with http(!)
That’s why it’s phrased in the future tense.
You don’t even know the half of this bug. You’re close, but you’re not there.
@Joel – “You don’t even know the half of this bug. You’re close, but you’re not there.”
So glad you could fill in the blanks for us…
@Joel Comment through obfuscation?
Diff it yourself if you’re so interested. Is it really so hard?
I bet “enterprisey” isn’t sounding so bad right about now, is it?
There is also a patch to the HTTP header handler. Isotopp wrote about this and I had linked it above, so Joel if that’s what you mean, then no, I have the whole bug, I only included the most interesting part of the patch above though. I’ll keep looking to see if we missed something though.
Peter, my speculation is that if you don’t have any file uploads and don’t have any untrusted local users (i.e. no shared hosts), then you are safe. But that may not be true if there is some Ruby file on the system that could be tricked into
eval()
-ing some code introduced by a different vector, or if there is already some existing file with dangerous code. Since it seems that you can execute any Ruby file on the system, that is a possibility worth considering.Joel, I updated the article. See above for why I missed the “schema.rb” issue. Out of sight, out of mind, I guess. You’re right though, I missed a big one.
Stop with the ludicrous criticism. It is not security through obscurity to give vendors a chance to get fixes out and give sysadmins a chance to get systems patched before full disclosure. It is reasonable, prudent and widely accepted; i.e. CERT and CVE.
When the patches are offered on trust, without explanation of what they do, it is reasonable to criticize. The 1.1.5 patch totally broke Rails Engines apps, and failed on a lot of Windows installs with latent zlib problems. If we had know what it did, we could have implemented app-specific fixes by hand instead and not had these problems.
I am not trying to be hard on the Rails team and I appreciate their work in fixing all of this. But nobody asked for a specific exploit.
“I mean if the core team says it’s critical then that should be enough reason to upgrade.”
Jan, I expect you maintain only trivial blog-type sites where downtime doesn’t matter. Upgrading is rarely trivial. Why should the whim of the core team ever be enough to force me to upgrade?
For one, they’ve already proven a lack of understanding by attemting a security-through-obscurity feint which, as we can see, has only wasted the time of Rails developers. When attacks are in the wild, as they are now, what point is there in trying to be cagey about exactly what needs to be fixed?
Further, notice how the core team originally tried to scare people into upgrading 1.0? Just imagine if you were running 1.0 and, based on their recommendation, risked upgrading to 1.1.5. When you discover that they were wrong and you had wasted your time, well, I hope you would be mightily pissed off.
The core team is quite incapable of determining whether any specific site should upgrade or not. Which makes sense. When to close the hole and how is a decision that needs to be made by someone who intimately knows the site in question and has up-to-date information on the security vulnerability. Which the core team has denied us.
So, boo to the core team for handling this vuln in a slipshod and unprofessional way. Cheers to Evan for giving us (some) the information we need to repair our sites.
I hope DHH learns from this mistake.
I think the Rails team acted as they should. The initial secrecy was to make sure script kiddies wouldn’t go around breaking all the public facing Rails sites they could find. Waiting one day before full disclosure was a good call in this case.
Everyone running extremely large sites should be able to perform a diff on the code and figure the problem out for themselves, and if they can’t they should be happy to wait a day for a patch and full disclosure.
About the schema.rb: what is that file doing in a production installation anyway? If you use migrations (which you should in my opinion) the schema.rb is just a cache for creating your test database. It should never be in your source repository and never ever be in your production install.
I think that
rake migrate
recreatesschema.rb
no matter what environment you’re in, so if you are using migrations to upgrade your production database losslessly, you are going to haveschema.rb
laying around even if you deleted it the last time you ranrake migrate
.Oop, sorry. You’re right, rake migrate always creates
schema.rb
even in production.How about an independent rails-full-disclosure mailing list? The core team is showing little interest in even acknowledging the debate here.
There’s always my RSS feed…
I don’t support Rails team security-through-obscurity. In my opinion people should be informed what is going on, and what are known bugs and fixes. Really nice post Evan!
In my opinion open source is about information availability. I don’t like partial disclosure approach that Rails team implements. That is not the way to improve security.
Why? The source is not open?
Guys, I appreciate your comments, but please realize this article is eight months old now. Things have changed a bit.
How much did the things really change? Is it maybe a time for a new post on the subject?