Sunday, 20 Aug 2006
Hello, Rails is Swiss Cheese
I investigated Ruby on Rails a
little bit and found that it's easy to introduce security holes into a
Rails application. (If you don't care about Ruby on Rails, you can
skip this entry.)
Surprisingly enough, despite all the hype around Rails, nobody seems to
have published a proper "hello world" script. (If you start by
running "rails someapp", you have already gone wrong, because
that generates 44 files. Hello world has to be a single file.) So,
here's "hello world" in Rails as a CGI script:
#!/usr/bin/ruby
require 'action_controller'
class Hello < ActionController::Base
def index
@who = params.fetch('who', 'world')
render :inline => PAGE
end
end
PAGE = <<END
<html>
<head>
<title>Hello</title>
</head>
<body>
Hello, <%= h @who %>!
</body>
</html>
END
Hello.process_cgi(CGI.new, session_options=false)
Actually, that's slightly more than a hello world script, because I've
added the ability to change "world" to some other name by passing in a
"who" parameter.
I won't explain how it all works because the documentation for ActionController::Base covers it pretty well.
The main difference between this script and a normal Rails application
is that I've included the template directly in the script, using a here document. (Normally, Rails gets its templates from a
template directory.)
The template is written in a language called eRuby,
which can be used to generate any kind of file, not just HTML.
The "h" appearing in the template is a function call that adds
html escaping. (It's defined in ERB::Util as an abbreviation for html_escape.)
It's very important to call "h" for every template variable in an
eRuby template, or there will be a serious security hole. If you
leave it out, an attacker can embed JavaScript in the page and steal
cookies from users who visit your website. Stealing cookies can be
very bad when they're used for session tracking, because then the
attacker can break into the user's account.
Having to religiously escape template variables everywhere is a design
flaw common to many template languages. Escaping should happen by
default, so that the template writer doesn't have to worry about it!
But most people who write template languages don't bother with this,
and it's a sad commentary on the state of web security today that
dangerous template languages have become standards. (This includes JSP
and ASP - it's not just Rails.)
Luckily, alternative template languages aren't hard to find. For Rails,
the easiest alternative is the "Builder" template language. It's
normally used for generating XML, but we can also use it for
HTML. Here's "hello world" using Builder:
#!/usr/bin/ruby
require 'action_controller'
class Hello < ActionController::Base
def index
@who = params.fetch('who', 'world')
render :inline => PAGE, :type => 'rxml', :content_type => 'text/html'
end
end
PAGE = <<END
xml.html {
xml.head {
xml.title("Hello")
}
xml.body(
"Hello, " + @who + "!"
)
}
END
Hello.process_cgi(CGI.new, session_options=false)
Not only is this more secure, but you get a nice side benefit: pages
generated this way will probably be well-formed XML, which makes it
easy to parse them in unit tests.
However, it turns out that Rails comes with an old version of the
Builder library that also has a security hole. The problem is that it
escapes regular text, but not the values of attributes. To
demonstrate how attributes values can be a security problem, I'll add
a form where you can type in the name to be used in the greeting:
#!/usr/bin/ruby
require 'builder'
require 'action_controller'
class Hello < ActionController::Base
def index
@who = params.fetch('who', 'world')
render :inline => PAGE, :type => 'rxml', :content_type => 'text/html'
end
end
PAGE = <<END
xml.html {
xml.head {
xml.title "Hello"
}
xml.body {
xml.p "Hello, " + @who + "!"
xml.form {
xml.p "Please enter your name:"
xml.input(:type=>'text', :name=>'who', :value=>@who)
xml.input(:type=>'submit', :value=>'Go')
}
}
}
END
Hello.process_cgi(CGI.new, session_options=false)
In this script, I've avoided the security hole by adding the
require 'builder' statement. This causes Rails to use the
version of Builder that I installed as a gem, instead of the old
version that Rails comes with.
What this goes to show is that if you want security, you need to test
it yourself. It's also important to write an end-to-end test to
document what the hole was and make sure it stays fixed.
Otherwise, you never know when upgrading a library will break things
again.
For example, versions 1.1.0 through 1.1.5 of Rails had a more serious security hole. Someone who did their due dilligence
using 1.0 would have been exposed by an upgrade. And given how long
it took for that one to be discovered, it seems pretty likely that
there are still more security bugs out there.
respond | link |
/code
|