How To Use SSL To Secure Your Rails App Against FireSheep And Other Evils

The post on Hacker News today about FireSheep, a Firefox addon which lets you trivially compromise the web application cookies/sessions of anyone on the same wireless network, gave me the much-needed impetus to upgrade my business to SSL security.  For the last several years, I haven’t encrypted traffic between the server and the user.  My theory was that my customer’s didn’t store anything security critical in their elementary school bingo card games, but the increasing amount of information available to the admin (me) plus a customer support story from this morning convinced me that compromise would be a Very Bad Thing.

Implementing SSL in Rails was very painful and resulted in my site being down or unusable for a large portion of my customers for much of the day.  (If I were doing it again, I would have paid the extra few bucks to set up a staging environment with its own certificate and verified everything worked on that prior to trying to fight my way through the process.) Luckily, since I am time-shifted from them by over 12 hours, few noticed.  In the interests of saving you and your customers some difficulty, I thought I would write up what I learned.

What You Need Before You Get Started

  1. A SSL certificate from a certificate authority which is trusted by the major browsers.  GoDaddy sells them for $12.99 and they are perfectly adequate.
  2. SslRequirement and AssetHostingWithMinimalSsl, both plugins by DHH.
  3. Rails to be fronted by Nginx.  The explanation for Apache is similar but the magic server configuration is different. (If you use Nginx+Passenger, can skip the Mongrel-specific bit below.)

Nginx configuration

Nginx manages configurations on a per-server basis, and cannot have a single server declaration listen to both HTTP and HTTPS requests. We’re going to get around having to copy/paste our entire configuration (and maintain it in two places) by DRYing it into a single external snippet, then including it twice.

Right now, your Nginx config looks something like this:

server {
    listen       80;

    //You have a lot of stuff here.
}

Cut everything out of the server declaration (yes, everything) and externalize it into a separate file. It should now look like:

server {
    listen       80;
    // This path is relative to the conf directory
    include apps/EverythingJustCut.conf;
}

You can now create a separate declaration for your SSL server without causing much overlap:

  server {
  listen 443;
  ssl on;

  #GoDaddy's instructions will walk you through setting these up
  ssl_certificate /usr/local/nginx/conf/ssl/your_certificate_combined.crt;
  ssl_certificate_key /usr/local/nginx/conf/ssl/your_key_pair.key;

    include apps/EverythingJustCut.conf;
}

I remember setting up the SSL certificate to be more of a nuisance than a difficulty, but if not, you can find very detailed instructions online.

Now, if you are using Mongrel, you need to do one more thing: withing EverythingJustCut.conf, you’ve got to find the place where Nginx is proxying to Mongrel and have Nginx set the X_FORWARDED_PROTO to https for HTTPS requests. This is the one and only signal that Rails, running on Mongrel, is going to get that a particular request is for SSL or not. This is trivial to do if you know you have to do it: just find all your existing proxy_set_header statements and add this after them:

  proxy_set_header X_FORWARDED_PROTO $scheme;  // http or https, evaluated per request by Nginx

Very Carefully Scrub Your Website For Absolute URLs

Absolute URLs containing the scheme (i.e. anything starting with http:// ) are dangerous to your application, because if your page transmitted over HTTPS references a “certain type of resource” on HTTP, your browser may display a Scary Error Message to your users. Unhappily, the browsers get to pick what constitutes a security-critical resource.

The general rules for maintaining SSL security on HTTPS pages are:

  • Javascript files: must always be loaded from HTTPS
  • Image files: must always be loaded from HTTPS, except for Firefox and Safari
  • CSS files: must always be loaded from HTTPS, except for Safari
  • other files: may or may not be loaded from HTTPS

Do you think you’re going to remember that? Yeah, me neither. Hence, you don’t want any hardcoded http:// anywhere. Let Rails take care of it for you with AssetHostingWithMinimalSsl: all you have to do is be consistent about always loading e.g. images through the image_tag or image_path helper, CSS and Javascript through their associated helpers, etc, and you will always get the proper behavior. The tough part is that you’re going to have to take IE and drive through every page on your site verifying that it does not accidentally include a resource transmitted in the clear.

The configuration for AssetHostingWithMinimalSsl goes in your config/environments/production.rb file and is trivial:

config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
  "http://images%d.example.com",  #In this example, I have images1..images4 .example.com configured in DNS
  "https://www.example.com"  #Your SSL-secured domain
)

Note that it is very easy to miss a http:// URL hidden in a CSS file, Javascript file, or analytics-package JS include somewhere. If you do that, even in an unused CSS selector, you will cause the browser to throw Big Scary Errors. Test that you have gotten everything very thoroughly prior to proceeding.

Require SSL for Any Security Sensitive Actions

Why are we requiring SSL? To prevent against an attack where the bad guy can sniff the cookie out of there air, thereby appropriating it for himself and logging in as the logged-in user (either by compromising a session ID or, in Rails, compromising the user_id you probably stored in the tamper-proof CookieStore). So what do we have to SSL? Everything Rails sends or accepts a session cookie with. What is that? Everything that an actual browser can access. (If you are, like me, in the unenviable situation where some URLs are going to be hit by user agents which cannot support either HTTPS or cookies, don’t forget that requiring SSL for all actions won’t help you.)

The SslRequirement plugin makes it easier to do this sitewide than it otherwise would be:

#in application_controller.rb
 unless RAILS_ENV == "production"
    def self.ssl_required(*splat)
      false
    end

    def self.ssl_allowed(*splat)
      false
    end
  else
    include SslRequirement
  end

This sets it so that SSL is required when we say it is in production only, and in other environments the statements which set up SSL requirements are merely silently ignored. Those functions are:

  • ssl_required: takes a list of :symbols representing action names to require SSL for. Should be nearly all of your actions. If someone tries to access one of these actions over HTTP, they will be redirected to HTTPS (+).
  • ssl_allowed: takes a list of :symbols representing action names to allow SSL for. If they aren’t required and aren’t allowed, then they’ll be redirected to HTTP if they try getting to this action over HTTPS.

You set them in each controller, on a per controller basis. There does not appear to be a handy mass assignment option like :all for this method, unlike most of the before_filter and similar things you find in Rails.

Note there is a subtlety here: if you let Rails share a session cookie over both HTTP and HTTPS, then if it is ever used over HTTP, byebye cookie security. This means you can either be very, very careful that you never let anyone access Rails actions over HTTP (and you pray a malicious attacker never tricks your users into clicking to a valid link to your website), or you ban your session cookie from being sent over HTTP at all:

#production.rb
config.action_controller.session = {
    :key     => 'name_of_session_goes_here',
    :secret          => 'you need to fill in a fairly long secret here and obviously do not copy paste this one',
    :expire_after    => 14 * 24 * 3600, #I keep folks logged in for two weeks
    :secure => true #The session will now not be sent or received on HTTP requests.
  }

This option will probably break your site, possibly subtly, the first time you switch it on. Test thoroughly. I haven’t got it 100% working for myself yet.

Host downloadable files on SSL? You just broke IE.

After several hours of frustration, failing my way forward, and finally getting things working on Chrome/Firefox, I received a bug report from an IE using customer who couldn’t download her bingo cards. Some deep Googling revealed that IE, for architectural reasons known only to the IE team, does not play well with downloadable files over SSL unless you set some very specific headers:

  response.headers["Pragma"] = " "
  response.headers["Cache-Control"] = " "
  response.headers["Content-Type"] = "application/pdf"  #Put your favorite MIME type here
  response.headers["Content-Disposition"] = "attachment; filename=#{filename}"  #
  response.headers["X-Accel-Redirect"] = "/path/to/file.pdf"
  render :nothing => true

Note I am using X-Accel-Redirect to have the file served directly through Nginx, rather than through Mongrel, as described here.

Conclusion

I hope that saved you and your customers some pain and insecurity. If you haven’t done this yet, and I think there are many Rails apps as open to exploitation as I was until this afternoon, I suggest you download FireSheep and see how quickly any idiot with wireless can compromise your admin session. Then, fix this as soon as possible.

About Patrick

Patrick is the founder of Kalzumeus Software. Want to read more stuff by him? You should probably try this blog's Greatest Hits, which has a few dozen of his best articles categorized and ready to read. Or you could mosey on over to Hacker News and look for patio11 -- he spends an unhealthy amount of time there.

25 Responses to “How To Use SSL To Secure Your Rails App Against FireSheep And Other Evils”

  1. Patrick Tescher October 25, 2010 at 11:51 am #

    Or if you use something like Heroku its just a checkbox (and maybe some cash): http://addons.heroku.com/ssl

  2. Boots October 25, 2010 at 11:52 am #

    This information is not Ruby specific, but any application which is not SSL encrypted and uses Session cookies for user login is open to the same attack. The setup would certainly be different for other webservers, but the overall application changes would be the same (no http assets, a verified SSL cert, etc).

    I know you didn’t say it was a Ruby-specific problem, but I figured some less security-versed folks might assume that when reading an article specifically for Ruby SSL in reference to the new (cool looking) plugin.

  3. Jean-Francois Noel October 25, 2010 at 12:09 pm #

    Nice list of most of the impacts of switching to SSL. Thanks, I’ll use it as a list of checks to do.

    You could have just deploy a staging environment with a self-signed certificate to test that everything was working with SSL and then do the switch in production. Some configurations, but no added cost.

  4. Stephan Wehner October 25, 2010 at 12:29 pm #

    Thanks, it’s nice to see that nginx makes it fairly easy.

    It definitely looks easier to start with SSL in mind from the beginning.

    Wouldn’t it be better and more transparent for the user to place sections to be protected under a hostname marked “secure” (which gets its own cookies, not shared with non-SSL areas of the site)?

    In the end it looks like hard-coded URL’s are the biggest hurdle (which Rails doesn’t make too difficult to avoid)

    Did you notice any performance problems?

    Stephan

  5. Joshua Estes October 25, 2010 at 12:42 pm #

    GoDaddy SSL Certs are $44.99 to $49.99 per year. I have no idea where you came up with $12.99. Even the page you linked to does not list them as that cheap.

  6. Vince October 25, 2010 at 5:05 pm #

    NameCheap is currently offering a free SSL certificate with every domain purchased from them (you have to click a link on the shopping cart page to redeem it). The cert can be used for any domain you own, not just the one you’re buying, and you don’t even have to redeem it right away.

  7. David Phillips October 25, 2010 at 5:53 pm #

    Joshua, Google for “godaddy ssl” and click the $12.99 ad to get that price.

  8. DaveA October 25, 2010 at 10:20 pm #

    SSL certificates are free. http://www.startssl.com/

  9. Nicholas Hebb October 26, 2010 at 3:09 am #

    Not all SSL certificates are the same. There are different types, depending upon the encryption and verification level. Plus you pay more for higher levels of indemnity coverage. This Wikipedia page compares the major ones, but the prices probably aren’t kept up to date:
    http://en.wikipedia.org/wiki/Comparison_of_SSL_certificates_for_web_servers

  10. PK October 26, 2010 at 5:11 am #

    Anyone can make an SSL cert–Windows has a makecert tool that does it and I’m sure other systems do as well. Browsers won’t trust it and you’ll get the nastygram about “suspicious web site”. But it will protect the site just fine.

    What you pay for is some trusted site vouching for you.

  11. Jason October 26, 2010 at 4:31 pm #

    FYI Heroku SSL instructions http://docs.heroku.com/ssl

  12. Jeremy Gray October 27, 2010 at 8:13 am #

    FYI – AssetHostingWithMinimalSsl, at least in the forms I found in both the DHH original and the GH fork I ended up using, causes mixed content warnings in Firefox on both Mac and PC, at least with the Firefox versions I’m running here. You’ll need to make a small, self-evident change to the lib and will, in the end, only serve non-ssl assets to Safari users.

  13. Anonymous Drive-By Editing October 27, 2010 at 9:25 am #

    Search the article text for the word ‘withing’, then delete this comment.

  14. Lawrence Sinclair October 30, 2010 at 2:51 pm #

    I will admit I didn’t read all the details above, but I did notice that you suggest having a site have some parts SSL and some parts non-SSL. Doesn’t that expose the cookies to session hijacking in the non-SSL part of the session? This is supposedly a vulnerability of some sites that are susceptible to firesheep, even though SSL appears to be enforced throughout the session.

  15. Chaitanya Gupta November 1, 2010 at 10:24 am #

    How has the switch to SSL affected your bandwidth and resource consumption?

  16. Patrick November 2, 2010 at 1:25 am #

    The impact is below negligible, Chaitanya.

  17. Chaitanya Gupta November 2, 2010 at 7:22 am #

    That’s great to hear! I thought that the switch to HTTPS might have been slightly bandwidth intensive as proxy caching, etc. cannot be used anymore. So I was thinking that using a signed request instead of HTTPS everywhere might also work e.g. this is how Amazon AWS does it http://aws.amazon.com/articles/1928#HTTP (assuming that the secret key was transferred to the client over SSL and stored locally)

    But if the impact of using HTTPS is negligible, best to stick to that I guess.

  18. Chris November 7, 2010 at 1:00 pm #

    In the code snippet where you list all the headers you had to set specifically for IE, you don’t say in what file you put those. It looks like it’s in a Rails action, since you’re saying render :nothing => true, or possibly a before_filter, but then you mention using X-Accel-Redirect in Nginx to serve the files directly. Can you please clarify? I’d like to implement this.

    Thanks!

  19. Patrick November 8, 2010 at 1:40 am #

    All of that stuff is inside a Rails action, Chris. Most of the headers go directly to the client. The X-Accel-Redirect one tells the Nginx that is in front of your mongrel “grab this file off the disk and give it to them, without tying up my mongrel during the potentially long download process”

Trackbacks/Pingbacks

  1. TempusFactor » Blog Archive » links for 2010-10-25 - October 25, 2010

    [...] How To Use SSL To Secure Your Rails App Against FireSheep And Other Evils: MicroISV on a Shoestring (tags: rails ssl) [...]

  2. HOWTO: Force https/SSL for Apache2, Phusion Passenger and Rails | Hints and Kinks - October 25, 2010

    [...] a lot of buzz right now about Firesheep and non-secure Rails [...]

  3. Sysadmin Sunday #3 « Boxed Ice Blog - October 31, 2010

    [...] How To Use SSL To Secure Your Rails App Against FireSheep And Other Evils – protecting your site from Firesheep [...]

  4. Delicious Bookmarks for November 12th from 03:01 to 03:06 « Lâmôlabs - November 12, 2010

    [...] How To Use SSL To Secure Your Rails App Against FireSheep And Other Evils: MicroISV on a Shoestring – November 12th ( tags: ssl rails security nginx ruby firesheep programming webdev howto secure ror app webapp guide tips tricks tutorial ) [...]

  5. [delicious | grep links | blogger] for 2010-10-30 at The Standard Output - November 13, 2010

    [...] el-get Pymacs Recipes Mechanical Keyboard Guide – Overclock.net – Overclocking.net How To Use SSL To Secure Your Rails App Against FireSheep And Other Evils: MicroISV on a Shoestring the creative internet (106 things) Reading X509 Certificates from remote machines INI Files Meet [...]

  6. How can we tell whether a webpage is secure? | WOPLL - November 23, 2010

    [...] How To Use SSL To Secure Your Rails App Against FireSheep And Other Evils (kalzumeus.com) [...]

Loading...
Grow your software business:
(1~2 emails a week.)