Handling SQL injection and XSS in Rails

I've been using Ruby on Rails for a good year and a half now, but something I still remember from when I was just getting started was the lack of discussion about SQL injection or XSS issues. This isn't very surprising; in any language or framework the last thing you explain to beginners is a rousing study on security concerns. It's kind of sad, really, when you think about it. But so is life. Let's look at code.

SQL Injection

Your basic ActiveRecord find goes something like this:

@articles = Article.find(:all)

That'll find all of your articles and populate them into @articles. Cool beans. Say you want to grab all the articles that are titled a certain way, which you have access to via params[:title]. There's a few ways of doing this. One way is to add :conditions.

@articles = Article.find(:all, :conditions => "title = #{params[:title]}")

So what you're doing is grabbing :title from your parameters and then directly tossing it in your query. Repeat with me: bad code. This is vulnerable to what's called an SQL injection attack, meaning anyone can come along and look for a post with the title of:

1; DROP TABLE super_cool_table;

So, like, don't do this.

One solution is to use ActiveRecord's magic, super-cool finder:

@articles = Article.find_by_title(params[:title])

This is fine. Rails automatically makes sure that any arguments you pass in won't be able to inject arbitrary SQL. For a bit more flexibility, I go the following route:

@articles = Article.find(:all, :conditions => [:title => params[:title]])

...or even:

@articles = Article.find(:all, :conditions => ["title = ?", params[:title]])

These are both a-okay. Rails sanitizes the parameters in each of these scenarios, so you can rest assured that you're not wide open to some ugly attacks.

XSS (Cross-Site Scripting)

I recently discovered a XSS hole in PayPal's own website, which theoretically would let me set up an attack that would let me steal your cookies, hijack your PayPal session, and do lots of Bad Things to your PayPal account and linked credit cards and bank accounts (probably involving hookers, a flight to Panama, and a fancy new yacht). (By the way— if anyone has a better idea of getting in touch with PayPal, let me know... it's surprisingly difficult to sift through regular customer support with issues like this.)

Anyway, I digress. For more information on XSS, check out the ever-popular Wikipedia article. You're here to combat XSS anyway, right? Let's get started.

In your views:

<%= h @article.title %>

That's it. The method "h" escapes any HTML that might be in your article's title that a wayward user might have submitted.

Granted, it's a bit of a pain in the ass. Django decided to auto-escape your values by default, and Rails does not. And the fun with XSS is that if you miss one of those little h method calls, well, you're screwed.

There's a few ways around that, though. One is to make sure you sanitize user input on the way into your app, so when you call it later on you know that you're safe. Another somewhat unequal way of doing things while maintaining interactivity (like embedded links and images) is to do the same thing I'm doing on this blog for its comments: allow users to use Markdown (or Textile) instead of HTML.

Another option is to go the plugin route and let it handle sanitization for you:

New layout, new blog

So I've been wanting to change up the blog a little bit over the last month or so, and I did a spur-of-the-moment thing the last few days and wrote my own blog. I moved from SimpleLog, which I enjoyed but got a little annoyed with some of the performance issues. It was also a bit too much for what I needed, really... it just feels nicer to cut out the bloat and have a nice, quick blog.

So give it a gander and let me know what you think. There'll likely be a few bugs here or there; feel free to let me know and I'll get them fixed up. Since it's now my codebase instead of someone else's I'll probably add some nifty new playthings to the blog every now and then for the heck of it.

This also means that my long infatuation with Apache is no more; this blog now runs on the hip new trendy web server nginx. If I keep going with the trendiness, well, before long I'll be running everything off of git, Merb, Rubinius, S3 and EC2.

It is, of course, written with Ruby on Rails.

Now that I'm done with this whole coding thing, I'll be back to writing super informative blog posts shortly. I've got a bunch of interesting topics to write about that I've been holding off on until I finished the new blog.

Automating your Rails workspace setup

When working with Rails, you can really end up with a lot of things going on at once. When I'm coding, I'll usually have Terminal open with my Mongrel server instance running, a tab with autotest with RedGreen and Growl integration running, and another tab open for me to run other script/generate commands or script/console commands. On top of that, I'll also have my trusty TextMate instance running in a different space and Safari actually running the app in a different space. It's a rather sweet setup, but it's a pain to set it up in the beginning. Naturally, you want all of this to be as painless as possible so that it's just easier to jump in and code when you want to.

I looked into pretty much every way to go about automating this. There's plenty of ways to give it a shot, too- first was Quicksilver and trying to tie together a number of apps in one hotkey grouping, but it didn't give me enough control over running commands in Terminal (specifically with keeping tabs intact).

Next up was Terminal itself, specifically in terms of Terminal's Window Groupings, which ended up really sucking. It lets you save specific numbers of tabs in a certain window, but it doesn't let you automatically run different commands in each tab (you can run a generic command, like "cd ~/railsapp" for each tab which was partway there though). I even went so far to dig into the Terminal .term XML files to manually try to get it to work, but the changes didn't stick.

AppleScript was the next idea, of course. I'm no AppleScript guru, but I know enough to be dangerous. The problem was that Terminal, though much improved in Leopard, still didn't offer a lot of comprehensive ways via AppleScript to modify tabs and run commands.

I even looked at giving Ruby a shot, mostly through libraries like RubyOSA. It basically hooks Ruby into regular apps on your OS X install, so you can use Ruby to control iTunes, for example. Again, though, as it builds upon AppleScript, you run into the problem of getting a finer control of scripting Terminal without definitive AppleScript dictionaries.

After all of this, adjusting my bash profile ended up being the simplest, cleanest, and fastest way to tackle this problem. It's an area I really haven't gotten into very much, but luckily it's really quick to dive into for this simple problem.

Basically you set an alias for longer commands in your profile. If you want to handle it yourself, jump into your profile (create it if the file doesn't exist):

vi ~/.profile

From that point, it's really simple. Set an alias and type in the longer command. For example:

alias ss='script/server'
alias sc='script/console'
alias a='autotest -rails'

...and so on. I have a separate alias to change the directory to a specific Rails project:

alias gt='cd /path/to/railsapp'
alias gtss='gt; ss'

So it's pretty easy for me to use Quicksilver to open Terminal, type "gtss" to start Mongrel, command+T for a new tab, hit "gta" to start autotest, and so on. I can probably refine the process even further by using the "run command in Terminal" bundle within Quicksilver to consolidate the first few steps, too. As a final helpful command, you can start up TextMate and Safari, too:

alias gtm='gt; mate .; open http://localhost:3000'

It's not the perfect solution yet (I'd love to hit one Quicksilver command and spawn 3-4 Terminal tabs, Safari, and TextMate all at once), but I can start up everything in a few seconds compared to half a minute or more than doing it all manually. The result is that I can hop into my development environment much quicker, which means it's a lot easier to get down to business when I need to.

CD Baby, design, and Rails

Slashdot just had an article with a rather inflammatory post title: Thinking about Rails? Think again. The story was about Derek Sivers and his idea to rewrite PHP-driven CD Baby to Ruby on Rails. Two years later they've dropped Rails and moved back to PHP. He lists his reasons in his posting at O'Reilly.

Naturally, I like Rails a lot, and I've use CD Baby frequently, so I wanted to check out his reasoning. It wasn't very intellectually satisfying. It boiled down to "Rails doesn't work for our existing setup, so PHP rocks". As commenters on /. and the posting have mentioned, this was a clear cut case of not understanding the language or the framework before they jumped into things. If you're trying to maintain backwards compatibility, integration with other existing side apps written in another language, and don't want to move to a new server setup, here's a little hint: you might not want to move languages, regardless of which language it may be. Rewriting your entire system just for the sake of a rewrite or for perceived benefits that you haven't quantified is asking for trouble. Every time I rewrote Good-Tutorials (it's happened twice now) I had distinct goals in mind about what the new language might do for me, how to exploit new aspects of a framework or additional software I install, and so on. Derek might have had something like that in mind, but based on his posting it looks like he just wanted to jump into Rails for the sake of Rails.

Along those same lines... what is going on with cdbaby.com? I know they did a redesign a few weeks ago-- I wanted to include it in a blog post at some point but wanted to give them a chance to update their layout just in case. They tried positioning it as "intentionally bare" and "temporary", but that was a month ago. I'm a huge stickler for simplicity and an eye on usability, but there's a point where it's detrimental to your business. Their current layout looks like their stylesheet isn't getting loaded on the page. I can't see this as being anything but abysmal. You're a huge indy music company and you're likely trying to draw people away from buying mainstream albums from record companies. How can do establish that trust and get people to make that jump by having a site that looks like it was coded in an afternoon? It would be interesting to see what CD Baby's sales and conversion rates are compared to the month prior when it had at least some semblance of a design. Also interesting- those in the comments of that blog posting who seem to like the "design" say it increases usability. The big "but" here is that all of those people posting are regulars to the site- I would bet the response would be different if it were made by someone who's never been there before.

From WordPress to SimpleLog

So I've finally finished moving my blogging from WordPress, powered by PHP, to SimpleLog, which is powered by Ruby on Rails. Did it for a couple of reasons:

  • WordPress just feels a bit heavy to me. I'm just a solo developer who wants to toss his ideas out every now and then; I'd rather have a slimmed-down, simplified tool to do so. I don't need all the extras, really.
  • I'm not a PHP developer anymore, and I haven't been for, jeez, 3-4 years or so. I'm deeply in love with Rails, and it makes sense for me to consolidate in that regard. If I want, I can more easily peek under the hood and tweak some code (which I've already done a lot of), or I can easily pop my own code or plugins in it. Actually I've been thinking of writing a few plugins for it already; it'd be good for me to learn more about plugins in Rails and it'd be fun to give back to the community.
  • I can move this off to my regular host, Slicehost. Administratively it's a lot easier, and I just love their setup more than the Site5 account I had set up for this blog.

This definitely wasn't enjoyable, though. A 2-3 hour process from download to restyling to server setup to publishing turned into a 2-3 night affair. I was kind of assaulted on all sides... issues with installing my Rails stack on Slicehost, issues with SimpleLog's code, etc. Turns out a lot of the troubles stemmed from a borked deprec recipe, but things should be all sorted now and we should be good to go.

That's a good thing, since I've been wanting to type a few things up lately. We'll get to all of that soon enough. :)