This post originated from an RSS feed registered with Ruby Buzz
by Jonathan Weiss.
Original Post: Why you should upgrade to Rails 2.1
Feed Title: BlogFish
Feed URL: http://blog.innerewut.de/feed/atom.xml
Feed Description: Weblog by Jonathan Weiss about Unix, BSD, security, Programming in Ruby, Ruby on Rails and Agile Development.
If you are following my twitter stream you have probably read me rambling about a security hole in Rails < 2.1.
With Rails 2.1 out for a while I though I should describe the problem a bit as there are many Rails applications stuck in 1.2.3 or 2.0.
The story begins with me giving a talk about Ruby on Rails security at Dynamic Languages World Europe in Karlsruhe. While talking about SQL injection, Tobias Schlottke, a fellow German Rails developer, mentioned that he once saw ActiveRecord allowing random SQL in the :offset option. Everybody in the room agreed that shouldn't be possible or wanted.
Later that day I sat down with Steven Bristol and we tried to verify this. Playing with MySQL we only got ActiveRecord::StatementInvalid exceptions. So I installed PostgreSQL (damn you MacPorts!) and tried the same with PostgreSQL and SQLite. Both times I got straight SQL Injection. Looking through the code I found out that the :offset parameter was also prone to SQL injection:
# vulnerable controller code
User.find(:all, :limit => params[:limit])
User.find(:all, :limit => 10, :offset => params[:offset])
# with params[:offset] or params[:limit] set to '; DROP TABLE users;'
# you got a big problem ...
MySQL is also affected but thanks to its default setting of disallowing multiple SQL statements per API-call you cannot insert a new SQL clause. You can just manipulate the one executing by setting other parameters (like an offset).
I've seen a lot of code taking the :offset parameter straight from the HTTP params and all people I talked with thought of it being a big problem. Especially since the documentation speaks about :offset and :limit being two integers (so you, or at least I, would expect auto-quoting like done for User.find(params[:id])).
I wrote a patch that casts both :limit and :offset to integers and sent it to core@ hoping for it to be included before Rails 2.1. After a bit of discussion if this is a bug or a feature (as apparently :limit can also be '1, 5'), Steven got hold of DHH at RailsConf and of Aaron Bedra who apparently discovered this problem a couple of months ago. Aaron wrote a new patch that also checks for the '1, 5' syntax of :limit and David committed it for Rails 2.1.
Unfortunately Aaron's patch didn't include changing the MySQL adapter as it overrides the method that adds the limit and offset options. This isn't as bad as it sounds as MySQL will not allow multiple SQL statements by default. David later committed a patch of mine fixing MySQL in edge (and the coming 2.1.1).
So if the new features alone will not bring you to Rails 2.1, here you have another reason to upgrade.
For you guys stuck in 1.2 or 2.0 land, either always cast your :limit and :offset options, or use those patches: