Techniques to Secure Your Website with Ruby On Rails (Part 1)

Ruby Developer
This entry is part 1 of 3 in the series Techniques to Secure Your Website with Ruby on Rails

Techniques to Secure Your Website with Ruby on Rails

During the seven years I’ve been using Rails, the framework has exploded in popularity because it’s made creating powerful applications quick and easy. It seamlessly handles so many of the issues that used to absorb developers’ time, such as database integration, session handling, template render, etc. Rails does it all for us.

Well, no…

Rails provides a brilliant framework that give us the building blocks to do all of those things, but the actual implementation is down to us, and it is very easy to undo all of Rails hard work if you don’t know what you are doing. In this series of articles, I will explore how to build a secure website in Rails, highlighting the danger of trusting data and exploring best practices in protecting your users. We will look at how Rails protects us from potential issues, and how to make your system even more robust.

The series is broken into three articles:

  1. In this article, I explain some ways that hackers can manipulate data before it even reaches your application, how Rails protects us from this manipulation, and what you can do to protect yourself further.
  2. Next, we’ll dig into Rails a bit more and look at the methods that you need to use to protect yourself once the data actually gets into your application.
  3. Finally, we’ll look at rendering that data, and other Rails security issues you need to be aware of.

It’s all about the data!

Websites are all about data. The data comes in, it’s stored, and then it’s rendered out to the end-user. The worst mistake you can make when building a website is to trust that data. Don’t trust where it’s come from; don’t trust what it is; and don’t trust what it’ll look like when you render it. Whether you’re building a personal blog, or a big e-commerce site, the moment you start to gain popularity, someone is going to try and take advantage of you, and they’ll most likely do it by manipulating the data your site is using.

Before we start looking at how Rails deals with these issues, check your version of Rails is up to date. Rails is a library that you are using to build your website. In whatever sphere of development you work in, it is essential that you use the latest versions of libraries, as they often provide important security updates. This article presumes that you are using at least Rails 3.2.5. If you’re not, then you should consider upgrading immediately.

When is a user not a user?

You have no way of knowing who or where the data that hits your application is coming from. This is a fundamental principle of building a website. Authentication adds a layer of security, but you still have no guarantee that the requests hitting your site are from the authenticated user. There are many ways that hackers can masquerade as users and it is your responsibility to stop them from doing so.

Session Hijacking

I’m writing this while sitting in a coffee shop. I could start spying on the network traffic of everyone else in here in minutes. If your website’s url starts with “http://” and someone is logging into your website, I could intercept the login request and see their passwords in clear text. The only way to stop me from doing so is to use HTTP Secure on that login page, which will encrypt the traffic between the user and your website. If I snoop the packets then, I’ll see the data being sent, but I won’t be able to make any sense of it.

This protects the users’ credentials, and is a good start, but it still doesn’t protect the user’s session. I could simply wait until our susceptible coffee-drinker has logged in to your site, and then intercept and hijack their authenticated session, giving me access to their account. It is, therefore, absolutely essential to encrypt the whole of your site behind https. A few years ago this was discouraged because https was ‘expensive’ in processing time, caching and more, but now it has now become the accepted wisdom that security matters more than speed. In fact, with modern technology, the extra time the encryption takes is actually very small. On my sites, I tend to see a 20ms increase in page load time. I’m happy with that tradeoff for my users’ security.

So how do you implement this in Rails? There are three things you need to do. Firstly, you need to purchase an SSL certificate and setup your web server to support SSL. If you’re using Heroku, herokuapp sub-domains now have SSL by default. Setting up SSL on your own domain is easy and basically entails two commands:

Secondly, you need to tell your Rails app to only run on https. Rails makes it easy. Simply add the following in your config block in config/application.rb.

Finally, you need to make sure that all your assets and links are https. If you have http assets on an https page, the user’s browser will display a mixed-content warning in the browser bar. As normal, Rails does most of the work for you, but if you have any hard-coded “http://” internal-links or images, make sure you change them. If you have any hosted services that you link to (e.g. Typekit, external javascript libraries, external asset management through S3, etc), make sure those links are https too.

One final thought with regards to session hijacking: make sure your sessions expire in a reasonable time and that you give users the option to log out. If people are using shared or public computers, the last thing you want is a malicious person to come along five minutes after your user has left and make use of their account because the session is still active.

Storing Data in a Session

There are various session store types that you can use in Rails. Since v2, the default has been to store the session in a cookie. This is fast and scales well, but it does mean that you have to be very careful about the data that you store for two reasons. Firstly, cookies are limited to 4kb and so if you store too much in them, you will lose data. Secondly, cookies are stored on the client-side and therefore the data stored within the session can be viewed. Rails includes a SHA512 signature hash (seeded with a secret string stored on the server) to stop the data from being tampered with, but it has no way of stopping the data from being viewed. This means that if you decide to store sensitive data in the session, you are basically exposing it to the world.

For 99.99% of applications, all you need to store in a session is the user’s id. This is perfect for cookies and why cookie storage is Rails’ default. If you have a real, solid reason to store other data in the session (and you really have to question whether that the reason is a good enough one), take the safe route and store the session data in the database. You can make the change it in config/initializers/session_store.rb:

You will need to generate the session database as well:

Session Fixation Attacks

Session fixation attacks are rare but deadly. They involve a hacker setting up a session (normally achieved by just visiting as site) and then overriding a real user’s cookie so that they both share that same session id. Once the real user has authenticated (and therefore that session is authenticated), the hacker can access the site as if he is the real user. The Rails security guide explains this in more detail.

Protecting against these attacks is easy. If you are using sessions to simply store the user_id for authentication purposes, then you can simply destroy the session and give the user a new session when they log in. This stops the hacker from sharing a session with an authenticated user. To achieve this, simply add this to your sessions controller:

Any authentication gems should do this for you, but it’s essential that you check yours does. The flavour of the moment, Devise, has protected against session fixation attacks since version 1.1.4.

Cross-Site Request Forgery (CSRF) Attacks

CSRF attacks have been witnessed in the wild since the turn of the century and are incredibly dangerous. Rails does a lot of work to protect against these attacks. However, it’s important to understand how the attacks work and make sure you don’t weaken your application to them.

A CSRF attack occurs when a third-party redirects the user to a destructive URL. An example:

  • On Twitter, users can update their profiles with a PUT request to http://www.twitter.com/settings/profile.
  • I place a link on my website such as:
    <a href="http://www.twitter.com/settings/profile?_method=put&email=l33t@hackers.com">Win a million pounds<a>.
  • The user logs into Twitter, reads his latest tweets and leaves.
  • He then visits my site, clicks on my special link and in doing so, updates his email address on Twitter.
  • I now head to twitter, click on the “forgotten password” link, specifying “l33t@hackers.com” as the email, recieve a new password in my inbox, and obtain access to the users account.

This is a bad thing.

To avoid this, Rails creates a per-session “authenticity_token”, which is required for every non-GET request to the server. It then silently inserts this token into every form generated by form_for or form_tag, onto any links that have a method: "POST", etc, and into Javascript callbacks using the jquery_ujs asset that is automatically inserted into your application.js. If you want to see an example of this, view the source of the Twitter page in my example to see it on a live site. By default, in your application_controller, there is a single line containing: protect_from_forgery. This is what tells Rails to check for that token on every non-GET request. In only the most unusual circumstances should you remove this line of code. Doing so would render your whole application vulnerable to CSRF attacks.

So if Rails does all this for you, what do you have to do yourself? Well, there are two things to make sure do. Firstly, you should ensure that if you use any external Javascript libraries that do not use jQuery for their AJAX callbacks, that you manually insert the authenticity_token. The token’s name and value are stored in meta tags for you, so you can craft a URL as follows:

The second thing is more complicated and deserves a section to itself.

Use RESTful Routes

The Rails API gives us a valuable piece of information about protect_from_forgery:

Bear in mind that only non-GET, HTML/JavaScript requests are checked.

If you create a method that has any changing, or destructive properties, and it can be accessed via a GET request, you are heading for trouble.

Let’s say we are creating our own settings functionality. We have a controller and routes file as follows:


Your application is now totally open to CSRF attacks. I can redirect a user to /update_settings?email=l33t@hackers.com and the email address will be changed. If you use match in your routes, then your code may well be vulnerable. This is such a big issue, that match is going to be removed from Rails 4, instead requiring you to specify the methods as per:

That’s better, but we can do better still. We can use RESTful routes, Rails style. If we treat settings as resource, we can specify the same URL for both routes, and have the functionality determined by the method. This is how Twitter’s settings/profile page, which we mentioned above, works. Doing this in your routes.rb is easy. Just tell rails you’re working with a resource:

We now have one url: /settings, which we can use to view or update our settings, using GET or PUT. Rails’ protect_from_forgery will kick to protect us against CSRF when the PUT update is sent.

Trust No-one!

By following the advice above, you will protect your users from the majority of known threats, and help guarantee that your requests are coming from an expected source. However, it’s essential to remember that even if you follow all this advice, your site will still be vulnerable. The hackers are always looking for the next step clever technique to take advantage of your site, and we are always playing catchup in defending ourselves.

In the next article, we’ll dig into Rails a bit more and look at the methods that you need to use to protect yourself once the data actually gets into your application. In the third part, we’ll look at rendering that data, and other Rails security issues you need to be aware of. In the mean time, start securing your application…

Techniques to Secure Your Website with Ruby on Rails

Techniques to Secure Your Website with Ruby On Rails (Part 2) >>

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • http://mrcasals.github.com Marc Riera

    For the first problem, which you solve by using SSL, couldn’ you just use the Digest authentication method?

    AFAIK, using the Digest method the client never send the password in plain text, but please correct me if I’m wrong.

  • http://robert-reiz.com Robert Reiz

    Great article. Thank you for writing it. I didn’t know that think with the match keyword. I replaced it immediately in my Rails Apps.
    Many thanks for the advice.

  • http://www.ihid.co.uk Jeremy Walker

    Marc – you could use the Digest method. However, IMO authentication pop-ups are ugly, it uses MD5, which is broken, and it doesn’t protect you from lots of other issues (good analysis on this Stack Overflow question). You could manually encrypt the password client-side before sending it, but then you’d still need SSL to protect when you sign up to a site, else someone else could intercept the request and change the password that’s been entered. I’ve never used Digest authentication, so please correct me if I’ve got something wrong.

    Robert – I think the main point people have taken away from it is the “match” keyword. I’m glad you found it helpful :)

  • Theodoros Orfanidis

    Match also accepts a via option. You could change your routes to:
    match ‘show_settings’ => “settings#show”, via: “get”
    match ‘update_settings’ => “settings#update”, via: “post”

    But I agree, the default is to match all HTTP verbs, which is not safe. It’s better to have the user specify it explicitly.

  • http://www.ihid.co.uk Jeremy Walker

    Theodoros – I wasn’t aware of that. Thanks for the info!

  • PapePathe

    Thanks for the enlightement on “match”.

    Great Article, waiting for the next episode.

  • stephen murdoch

    Very helpful article, which has made me extremely paranoid! Thanks for that!

    What options do you have if you’re on Heroku, and don’t want to pay $240 a year to enable their SSL endpoint addon?

    My site is for a non-profit organisation and they can’t afford this expense. Is it possible to have security on Heroku without it costing so much?

  • http://www.ihid.co.uk Jeremy Walker

    PapePathe – Thanks :)

    Stephen – Excellent. Paranoia is king! Regarding SSL on Heroku. You could use Piggyback SSL (free) and have your domain name redirect to *.herokuapp.com. Not ideal but otherwise I can’t see another way, other than asking them nicely and hoping someone there is in a generous mood to non-profits! :) If that doesn’t work, maybe you have to look for an alternative host.

  • http://konccepts.com Som

    Great article and special thanks for the twitter security advice ..Now thinking to implement what you have advised …thnx a lot

  • http://homakov.blogspot.com homakov

    @Jeremy, you cannot rewrite http verb with GET. ?_method=put will not work. Rewrite works only for POST.

    Also it’s me who removed match http://homakov.blogspot.cz/2012/04/whitelist-your-routes-match-is-evil.html

  • Victor

    Do you know how to do it with rails 3.0.7 ? Thanks.