Posted by Nick Sieger Tue, 15 Aug 2006 04:12:00 GMT
What was the biggest security threat story for me last week? No, it was not the disrupted liquid bomb plot, it was the Rails security hole that caused quite a brouhaha among the Ruby community. (Guess that shows my increasing tendency to lose touch with reality. Maybe a sign of the miserable state of unrest in the world and how living in the land of the world’s only super-power makes it easy to turn the other cheek? Or...ok, ok...it’s just me.)
From my view of the Rails security issue, there are actually quite a few interesting angles that came out of this story.
Rails is Growing Up
This is the obvious one. The first major fault to be discovered in Rails shows that Rails the codebase, Rails the core team, Rails the technology stack, and Rails the community is going through growing pains. David was both praised and criticized widely for his handling of the disclosure. Many rightly complained that the initial announcement didn’t give system maintainers enough information to decide whether the risk warranted disrupting normal operations to spend time to test and roll out the patch. This was compounded by the fact that the initial announcement did not identify versions affected and instead assumed all past versions, which turned out not to be the case.
Others thanked the Rails team for their discretion and trusted the recommendation despite the fuzziness and lack of details. These folks either were able to perform the upgrade much more easily or had some inkling of just how serious the issue was.
The aftermath showed that the Rails core quickly learned from the experience. A security mailing list and google group were set up for future incidents and David promised to apply more rigor and policy to future announcements.
It seems pretty obvious that the size of the gaffe was such that to expose the details immediately would have had way too much potential to cause widespread data loss and denial of service. In fact, the nature of the bug strikes me as one of those embarrassing bugs that every software developer commits at one point in their coding life where you amaze yourself at the short-sightedness of your implementation. I think the initial message could have been dispatched with information on the severity of the threat without necessarily disclosing the exact exploit. So, essentially I agree with the approach that was taken, but the message left out details required to evaluate the threat.
Two early blog posts came out the day after claiming to know the details of the exploit. It turned out that they didn’t quite understand what was afoot. (Although Evan Weaver has since updated his post to clarify his original analysis.)
The threat turned out to be a simple remote code execution issue. The
:controller dynamic expansion aspect of routing contained a bug that
allowed arbitrary .rb files in a Rails application to be executed
undesirably. By far the most dramatic consequence would be
experienced if one’s
db/schema.rb file were to be executed with a
/db/schema, causing your entire database contents to be
dropped and reloaded.
By examining the
safe_load_paths method defined in affected
versions, it appears that the implementation tried to limit elements
of the load path that matched the expanded
RAILS_ROOT of the
application. Combine this with the fact that other elements of the
routing system eagerly
require‘d files with inadequate
bounds-checking spells your recipe for disaster.
Many posters and commenters quipped that a simple
svn diff was
enough to give script kiddies or other black hats the information
needed to exploit the issue. Or was it? Given that the two early
analyses turned out to be off the mark, were people in the know
exercising more discretion by not disclosing more details?
Personally, I spent more than an hour staring at the affected routing code trying to untangle the various metaprogramming tricks and regular expressions that make up the Rails routing system. And I consider myself fairly adept at reading and understanding code!
The truth of the matter is that, unless you’re a member of core or have a high level of familiarity and involvement with the Rails codebase, the svn diffs provide far too little context to decode the actual problem.
Does this speak to the obfuscated nature of the Rails codebase or to the relatively advanced nature of web programming in Ruby? If I had to pick one, it would be the latter, but I’m leaning towards neither. The Rails codebase is not the most readable, comprehensible piece of code I’ve ever seen, but it does its job remarkably well. Perhaps if the routing code in question was a bit more understandable by the masses, this rather obvious security issue wouldn’t have gone undetected for so long.
Post-1.1.6 Release Triage
A group of enthusiastic Railsers jumped onto #rails-security on freenode shortly after the 1.1.6 release, where an effort had been organized to verify all the patches across various combinations of web servers and Rails versions. An IRC channel, a wiki, Ruby, Zed’s RFuzz, and a piece of code were all the tools required to get a distributed test verification process up and running. This sort of thing happens all the time in the open source world, with programmers around the globe pitching in to raise the triage tent of the MASH unit. Still, it was exciting to see and be a part of the action and to be reminded of the power of the collective whole working for a common cause.
Dynamic Routing Harmful?
Rails’s dynamic routing code came under fire too, understandably so. Maybe this is one case where the developer-friendly approach of magically recognizing URLs goes a little too far? Production-only routes that do away with the expandable path elements could easily be generated by visiting all the controllers in the codebase and generating a more static route for each -- sounds like a good idea for a plugin. Perhaps the controller is the better place to store routing metadata anyway?
class UsersController < ActionController::Base map_default_route # could be optional end class PostsController < ActionController::Base map_route_as_resource end
Sounds like good fodder for future investigation!