Insecurity is Ruby on Rails Best Practice

Update: This post is really really out of date. Please disregard most of what’s written here.

Ruby on Rails by default encourages developers to develop insecure web applications. While it’s certainly possible to develop secure sites using the Rails framework you need to be aware of the issues at hand and many technologies that make Rails a powerful easy to use platform will work against you.

Cross Site Request Forgery
CSRF is the new bad guy in web application security. Everyone has worked out how to protect their SQL database from malicious input, and RoR saves you from ever having to worry about this. Cross site scripting attacks are dying and the web community even managed to nip most JSON data leaks in the bud.

Cross Site Request Forgery is very simple. A malicious site asks the user’s browser to carry out an action on a site that the user has an active session on and the victim site carries out that action believing that the user intended that action to occur. In other words the problem arises when a web application relies purely on session cookies to authenticate requests.

Let’s look at a simple example. I want my site to add me to your 37Signals Highrise contact list.

37 Signals Highrise
Working out how to do this is pretty simple. I go to Highrise and take a look at the “Add a person” form in Firebug or using View Source. It’s a fairly straightforward form that submits to http://ianloic.highrisehq.com/people and has a bunch of fields. I can easily recreate that form on my own server. Since Highrise uses a different domain for each user I ask the user to enter their domain name. I use the same username I use everywhere else so it should be pretty easy to fool my user into giving me their Highrise domain name. Most other sites do not have unique per-user URLs.

The page is here and the source is here. Provided you’re logged in and you enter your highrise domain correctly clicking “Add me” will add my name to your contacts list because 37Signals’ servers have no idea that the request is not coming from their application.

It’s fairly straight-forward to modify a form like this to automatically submit on page load. It’s pretty easy to put it in an hidden IFRAME so the user doesn’t even know what’s going on.

By using a unique per-user URL the guys at 37 signals have made this exploit non-trivial. This is not the default behavior from the Ruby on Rails framework. By default action URLs are very predictable. For example if we take a look at the social bookmarking site Magnolia we see predictable URLs all over the place.

Magnolia
As soon as you log in to Magnolia you are presented with a form for adding a bookmark by typing in its URL. This is a fantastic user experience. Unfortunately for Magnolia’s users anyone that submits this form on their behalf can add bookmarks and the form’s action (http://ma.gnolia.com/bookmarks/quicksave) is the same for all users. A trivial page that adds a bookmark to a visitor’s Magnolia account would look like this (try it):

<html>
    <head><title>Ma.gnolia</title></head>
    <body onload="document.getElementById('f').submit()">
        <form id="f" method="post"
                action="http://ma.gnolia.com/bookmarks/quicksave">
            <input type="text" value="http://ian.mckellar.org/"
                name="url" id="url" type="hidden"></input>
        </form>
    </body>
</html>

But it gets even worse. Since by default Rails allows GET as well as POST submissions you can call an action from an IMG tag (try it):

<img src="http://ma.gnolia.com/bookmarks/quicksave?url=http://ian.mckellar.org/">

Magnolia is not unique in the behavior. Unless a site’s developer has gone out of their way to prevent it, this class of attacks will affect every Rails site. Most of the popular sites I’ve looked at exhibit some vulnerabilities.

Other Rails sites I’ve looked at attempt to do their input validation in JavaScript rather than in Ruby which leaves them open to JavaScript injection and hence XSS attacks. This is a far more serious attack that I can cover separately if there is interest.

Solutions
Easy Solutions
There aren’t any good easy solutions to this. A first step is to do referrer checking on every request and block GET requests in form actions. Simply checking the domain on the referrer may not be enough security if there’s a chance that HTML could be posted somewhere in the domain by an attacker the application would be vulnerable again.

Better Solutions
Ideally we want a shared secret between the HTML that contains the form and the rails code in the action. We don’t want this to be accessible to third parties so serving as JavaScript isn’t an option. The way other platforms like Drupal achieve this is by inserting a hidden form field into every form that’s generated that contains a secret token, either unique to the current user’s current session or (for the more paranoid) also unique to the action. The action then has to check that the hidden token is correct before allowing processing to continue.

This is a pain in the arse to write by hand.

What is really required at the Rails level is a of form API that can generate and consume forms securely. In Drupal all form HTML is generated and parsed by the framework. This allows application developers to protect themselves from XSRF without even knowing they are.

There’s a plugin called Secure Action but I’m not sure how well it works. The dependence on a static shared salt rather than a randomly generated secret in the user’s session concerns me. The way it puts the signature in the URL makes me nervous too. It’s better than nothing though.

OpenID for the mathematically challenged

The other day I got the OpenID bee in my bonnet and grabbed James Walker‘s module and installed it on my server. Actually I grabbed it from CVS, and then discovered that the CVS version is half-ported to some new Drupal 6 form API, so I ended up using the DRUPAL-5 tag.

Anyway, I use Dreamhost which I love for many many reasons (primarilly it’s really cheap and seems to work really well). Unfortunately they don’t build their PHP with BCMath or even GMP, which means my PHP can’t do the hard math that’s required for crypto. Luckily there’s a mode of OpenID that doesn’t require any work on the relaying party side. So I made a small change that allows James’ module to work in this “dumb” mode.

Index: openid.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/openid/openid.install,v
retrieving revision 1.2
diff -u -p -r1.2 openid.install
--- openid.install      25 Mar 2007 06:38:00 -0000      1.2
+++ openid.install      16 May 2007 22:59:56 -0000
@@ -2,24 +2,6 @@

/**
- * OpenID module requires bcmath
- */
-function openid_requirements($phase) {
-  if ($phase == 'runtime') {
-    $requirements['bcmath']['title'] = t('BCMath');
-    if (function_exists('bcadd')) {
-      $requirements['bcmath']['severity'] = REQUIREMENT_OK;
-      $requirements['bcmath']['value'] = t('Enabled');
-    }
-    else {
-      $requirements['bcmath']['severity'] = REQUIREMENT_ERROR;
-      $requirements['bcmath']['description'] = t('OpenID needs the bcmath extension for encryption.');
-    }
-  }
-  return $requirements;
-}
-
-/**
* Implementation of hook_install
*/
function openid_install() {
Index: openid.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/openid/openid.module,v
retrieving revision 1.2
diff -u -p -r1.2 openid.module
--- openid.module       25 Mar 2007 06:38:00 -0000      1.2
+++ openid.module       16 May 2007 22:59:56 -0000
@@ -133,10 +133,14 @@ function openid_login_form_submit($formi

$idp_endpoint = $services[0]['uri'];
$_SESSION['openid_idp_endpoint'] = $idp_endpoint;
-  $assoc_handle = openid_association($claimed_id, $idp_endpoint);
-  if (empty($assoc_handle)) {
-    drupal_set_message(t('OpenID Association failed'), 'error');
-    return;
+
+  // if we have BCMath, we should use OpenID smart mode
+  if (function_exists('bcadd')) {
+      $assoc_handle = openid_association($claimed_id, $idp_endpoint);
+      if (empty($assoc_handle)) {
+        drupal_set_message(t('OpenID Association failed'), 'error');
+        return;
+      }
}

Also, I put the patch up on Drupal.org

The Sidekick ID and the iPhone

There were two interesting announcements today. First the Sidekick ID which had been previously leaked was formally announced and reviews have started to show up. Secondly Apple announced that the OS X Leopard will ship three months late – more than two years after the previous release of OS X. This slip is being seen as evidence that Apple is having trouble building as many products at once as it wants to.

In the four years I was at Danger we were building exactly one product at a time. We failed to separate the development of the hardware, the OS and the applications. Separating the client and server schedules was a slow and painful process. In the two years since I’ve left things seem to have improved. The fact that they’re able to ship two products (even if they are quite similar) is really exciting. That Danger is succeeding where Apple, with their 30 years of experience, is beginning to stumble is cause for congratulations.

Making dynamic static pages

UPDATE: I changed how this works and blogged about it.
I wanted my home page to reflect what was going on in my life, or at least what content I was generating. There’s the concept of a lifestream floating around at the moment, but I was happy just to have a few sources (a couple of blogs, my bookmarks, my twitters and my flickr stream) shown, split out my source. The catch was I wanted to do it without implementing a web service to back it.

If you want to pull content from a bunch of different servers without writing any server-side code your only options are Flash and JSON. They’re both ways of getting around the web security model that’s protected us for so long. Flash is kind of complicated, requires proprietary, expensive tools to work with while JSON comes for free. The idea behind JSON is that we can use the browser’s tag to load a script from another serve that contains data encoded as JavaScript data structures rather than code.

A few key services like Flickr and del.icio.us offer JSON versions of their feeds, but most do not. In steps FeedBurner who in January added a JSON version of the feeds they serve, and since you can ask FeedBurner to host any feed you please we can use them as our high-availability, standardizing, caching feed proxy. I\’d already set up FeedBurner for this blog, so I just added feeds for my LiveJournal and twitter accounts and looked up how to get access to the JSON feeds for my Flickr and del.icio.us accounts.

When you call a JSON script you can often pass in the name of a callback function to be called when the data is returned. I wrote a simple one to process a JSON response from FeedBurner and turn some of the most recent items into HTML list items:

var max_items = 3;
var target = null;
var filter = null;
function fb(o) {
  if (!target) return;
  for (var i=0; i<o.feed.items.length && i<max_items; i++) {
    var item = o.feed.items[i];
    var li = document.createElement('li');
    var a = document.createElement('a');
    if (!item.title) item.title = item.date;
    if (filter) item.title = filter(item.title)
    a.appendChild(document.createTextNode(item.title));
    a.setAttribute('href', item.link);
    li.appendChild(a);
    target.appendChild(li);
  }
}

The function depends on a couple of external variables that are set before the JSON feed is called. They are target, the DOM object to append the list items to and filter an optional function to post-process titles. I had to add filter since my twitter feed comes back a little weird.

Getting the most recent posts from this blog into a bullet list is now pretty straight-forward:

<ul id="ianloic-list" />
<script type="text/javascript">
target = document.getElementById('ianloic-list');
</script>
<script type="text/javascript"
src="http://api.feedburner.com/format/1.0/JSONP?uri=ianloic&callback=fb">
</script>

Getting my LiveJournal in was identical and twitter just required me to write a filter function to chop up the title a little and do some unescaping. For some reason the twitter feed is both HTML entity encoded and JavaScript string escaped when I get it back from FeedBurner.

Flickr and del.icio.us each require some custom code since I want to handle each of them specially. For del.icio.us I link to the tag pages of each tag on each bookmark and for Flickr I embed a thumbnail for each image. Take a look at the source code of ian.mckellar.org to see how that’s done or drop me if you’d like more explanation.

Burning your Drupal feed in two easy steps

FeedBurner provides all kinds of neat stats, but it didn’t seem straight-forward to “burn” my blog feed since I’m using Drupal 5. After a little fiddling I think I’ve got a pretty good idea how to make it work in probably the simplest way possible. In fact, it doesn’t require and Drupal configuration at all.

  1. First I set up a FeedBurner account and burned my feed. The feed Drupal produces for me is: http://ianloic.com/rss.xml. Now when I access http://feeds.feedburner.com/ianloic I get the contents of that feed. It’s pretty simple, but so far nobody is going to see that feed.
  2. Then I simply told Apache to redirect all requests for that feed, except the ones from the FeedBurner bot to my FeedBurner feed. With the slight of hand magic of mod_rewrite this is pretty straight forward. In the root of every Drupal install there’s an .htaccess file containing a bunch of stuff. I just added a few lines to the mod_rewrite.c block of that file:
      # Rewrite rss.xml to http://feeds.feedburner.com/ianloic
      # unless FeedBurner is requesting the feed
      RewriteCond %{HTTP_HOST} ^ianloic\.com$ [NC]
      RewriteCond %{HTTP_USER_AGENT} !FeedBurner.*
      RewriteRule ^rss.xml$ http://feeds.feedburner.com/ianloic [L,R=301]

    This will cause Apache to send a 301 redirect to http://feeds.feedburner.com/ianloic any time anyone requests http://ianloic.com/rss.xml, unless their HTTP User Agent begins with FeedBurner.

    Now I’ve got access to all the FeedBurner statistics and fun features. Since I didn’t actually touch the Drupal configuration I’m pretty sure a similar approach can be taken to applying FeedBurner to any feed.

Tag Clouds Two Point Oh?

[flickr-photo:id=15085782,size=m] Tag clouds bore me. They’re a relatively effective way of indicating quickly what topics are popular but that’s it. From del.icio.us’ cloud I can see that the site is for nerds – web nerds specifically. Flickr’s tag cloud tells me that people tag events and place names but that’s about it. My personal tag clouds on these sites tell me even less. My del.icio.us tag cloud tells me almost nothing – its a huge block of dark-blue and light-blue text. The Flickr one isn’t much better – it tells me mostly that I took a bunch of photos kayaking in the Queen Charlotte Islands, or perhaps more specifically, I got around to tagging my kayaking photos.

I’m more interested in seeing what’s going on right now and seeing how these topics are related. Since this is a graph visualization exercize I threw graphviz at the problem. After a bit of preliminary experimentation I ended up defining a graph based on recent tags pulled from an RSS feed. Each tag is represented as a node and any tags which appear together on the same post have arcs between them. Tag text gets scaled up a little with frequency. The effect isn’t perfect. Its pretty boring when there isn’t much data like on this site:

With a bit more data, like from my recent delicious feed things can get cluttered but we can see what I’m interested in right now:

This idea isn’t fully developed. The complexity of laying these graphs out in a sensible manner increases pretty rapidly as the number of nodes and arcs increases and so does the visual clutter. I’d like to experiment with client-side graph layout (ie: implementing graphviz in JavaScript) and doing something more sensible with synonym tags – ie: tags which always appear together. Synonym tags are somewhat interesting, but can distract from the relationships between concepts. Treating all tags that are coincident over a small number of posts as synonyms may often result in false synonyms, and collapsing synonyms will make it easier to scale to more posts, so I expect that that may be a productive path to go down in scaling these visualizations up to encompass more posts.

Oh, and the final demonstration – my friend Dan is looking for and apartment and is a Ruby on Rails web application developer:

Syntax Highlighting for Drupal

[flickr-photo:id=252312738, size=m] While writing my last post, I felt the need to post some source code examples and I wanted them to be pretty. Looking around drupal.org, I failed to find what I wanted. There were a few options, the codefilter module, but that only supported PHP highlighting, the geshifilter module, but that doesn’t support Drupal 5.x which I’m running, or patches against codefilter to add GeSHi support.

So I did what was probably the wrong thing and wrote my own. At least I didn’t write it from scratch, I based it largely on codefilter, with some inspiration from the patches to codefilter that add GeSHi support.

I hacked up GeSHi a little as it wants to link keywords of most languages to reference sites. While this sounds like a good idea in theory it was linking HTML keywords off to some random site I didn’t really like and didn’t think was that good, so I disabled that functionality.

Using the module is pretty straightforward. You wrap your source code in tags that look like

<code language="LANGUAGE">...</code>

where LANGUAGE is a supported language. If there’s an enter in your block then it treats it as a block otherwise it renders it inline. Also, some whitespace is trimmed, so you can force a single line to be treated as a block by putting an enter at the start or the end.

Right now it’s being maintained in the same source control as I’m using for my web site, but I’ll move it into Trac and Subversion eventually. For the time being it’s attached.

Flickr for Dojo

I’ve been working on a little Dojo based application which talks to Flickr, so I put together a little library which uses Dojo to talk to Flickr using it’s rest JSON interface.

Use
It’s pretty simple to use, just include the JavaScript file:

<script src="flickr.js"></script>

Tell the library what your keys are:

flickr.keys(API_KEY, SECRET_KEY);

And you’re set to go.

The main entry-point is flickr.call. As the first argument, you pass in a hash of arguments, as described in the Flickr API documentation. The method you’re calling is included in this hash. The second argument is optional and is a callback to be called with the response from the Flickr servers. The response will come back in JSON format so it is easy to handle it in JavaScript. The Flickr JSON response format is discussed in detail on the Flickr site.

So what would all this look like? Something like this will load interesting photos from Flickr and add them to the current document:

flickr.keys(API_KEY, SECRET_KEY);
var pagenum = 1;
function interesting () {
    flickr.call({method:'flickr.interestingness.getList',
            page: pagenum, per_page: 10}, interesting_cb);
    pagenum++;
}
function interesting_cb (response) {
    if (response.stat != 'ok') {
        var error = document.createElement('div');
        error.appendChild(document.createTextNode(response.message));
        document.body.appendChild(error);
        return;
    }
    for (var i in response.photos.photo) {
        var photo = response.photos.photo[i];
        var img = document.createElement('img');
        img.classname = 'interesting';
        img.setAttribute('src', 'http://farm'+photo.farm+
                '.static.flickr.com/'+photo.server+'/'+photo.id+
                '_'+photo.secret+'_s.jpg');
        img.setAttribute('width', '75');
        img.setAttribute('height', '75');

        var a = document.createElement('a');
        a.setAttribute('href', 'http://www.flickr.com/photos/'+
                photo.owner+'/'+photo.id);
        a.appendChild(img);

        document.body.appendChild(a)
    }
}

Implementation
I’m not actually using all that much from Dojo. The main thing I’m taking is the crypto library, specifically dojo.crypto.MD5. The way I’m making the actual JSON calls is by appending elements to the page. Perhaps at some point I’ll move to using Dojo’s ScriptSrcIO but right now I’m not.

The current version of the code is attached: flickr.js