One of the things I don’t like about Rails is that it doesn’t auto-escape HTML in user input. Forget one
h call in your template and you’re screwed. Worse yet, before Rails 2.0,
sanitize were flawed. Fortunately that’s been fixed. Django added auto-escaping even though it was a backwards incompatible change, but so far there doesn’t seem to be similar movement on the Rails front.
But I’m all about automating manual processes. So let’s fix this problem.
Sanitize before saving or before displaying? Or both?
Should you sanitize text before saving it or before displaying it?
It’s nice to not need to worry about doing anything extra in your views. However, if a field escapes your notice, you may be open for an attack.
I think your first line of defense should be model-level sanitization, but auto-escaping HTML is good backup. Doing both covers your bases at a cost of extra processing.
xss_terminate is a plugin in that makes stripping and sanitizing HTML stupid-simple. It’s install and forget. And you can forget about forgetting to
h() your output, because you won’t need to anymore. It’s based on acts_as_sanitized by Alex Payne but updated for Rails 2.0, and with some new features.
I like acts_as_sanitized but it’s not being maintained any more so Alex gave me the OK to take his code and do something different with it. Here’s what makes
- It works with Rails 2.0.
- It’s automatic. It is included with default options in
ActiveReord::Baseso all your models are sanitized. Period.
- It works with migrations. Columns are fetched when model is saved, not when the class is loaded.
- You can decide whether to sanitize or strip tags on a field-by-field basis instead of model-by-model.
- HTML5lib support if Rails’s HTML parser isn’t doing it for you.
Here’s how you use it.
script/plugin install http://xssterminate.googlecode.com/svn/trunk/xss_terminate
Strip HTML tags from all the fields in a model
class Article < ActiveRecord::Base end
Done. All models have tags stripped by default.
Sanitize HTML from some fields
1 2 3
class Article < ActiveRecord::Base xss_terminate :sanitize => [:body] end
Use HTML5lib to sanitize HTML from some fields
HTML5lib is a new library for parsing HTML for Python and Ruby. Its goal is to parse HTML like browsers do, so it’s very fault-tolerant. If you want to use it,
gem install html5 and use the
:html5lib_sanitize option. This is thanks to code by Jacques Distler.
1 2 3
class Article < ActiveRecord::Base xss_terminate :html5lib_sanitize => [:body] end
But I don’t want to strip HTML at all from that field!
1 2 3
class Article < ActiveRecord::Base xss_terminate :except => [:title, :body] end
Putting it all together
And of course, you can put these options together. Remember, fields are stripped of tags by default, so that’s assumed unless you override it.
1 2 3
class Article xss_terminate :except => [:author_name], :sanitize => [:title], :html5lib_sanitize => [:body] end
Report bugs at the xss_terminate Google Code site.
Extra credit: Use Erubis
Erubis catches 80% of HTML escaping screw ups by making them impossible. You can use it in conjunction with xss_terminate or other XSS plugins to give yourself an extra layer of protection. (See our post on setting up Erubis with Rails 2.0.)
With Erubis, code like
<%= "<script>alert('pwnd')</script>" %> can be auto-escaped.
However, all Rails helpers which generate HTML must be called with
<%== %> so the HTML is not escaped. This leaves an opening for attacks like this:
<%== link_to user.name, "/some/url" %>
user.name contains XSS you’re pwnd.
So while Erubis is a marked improvement over Erb it’s not a cure-all. That’s why I like to use both approaches.
There’s been a lot of discussion about Rails and XSS lately, so I’m hopeful that the situation will get better. Here’s a couple other XSS protection projects you can check out:
- SafeERB – Throws exceptions if you try to display tainted strings. Call
- xss-shield – automatically
h()strings unless marked as “safe”.
- sanitize_params – strip HTML from your parameters before they hit your models.
- AntiSamy – another whitelist-based approach (not available for Rails)