- Techniques to Secure Your Website with Ruby On Rails (Part 1)
- Techniques to Secure Your Website with Ruby On Rails (Part 2)
- Techniques to Secure Your Website with Ruby On Rails (Part 3)
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.
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:
- 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.
- 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.
- 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.
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:
heroku addons:add ssl:endpointheroku certs:add my_cerficate.crt site.key
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.force_ssl = true
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
You will need to generate the session database as well:
rails generate session_migrationrake db:migrate
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:
# Authenticate user@user = #...# Destroy the existing session in case anyone is sharing it and# create a new session that you know to be unique to the user.reset_session# Store the user's id as normalsession[:user_id] = @user.id
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
- I place a link on my website such as:
<a href="http://firstname.lastname@example.org">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 “email@example.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_tag, onto any links that have a
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.
var form_data = //.. Serialise a form// Get token and param from the meta tagsvar token = $('meta[name="csrf-token"]').attr('content');var param = $('meta[name="csrf-param"]').attr('content');// Create url of "/settings/profile?name=Jeremy+Walker&authenticity_token=askdsalewg303y09sd00dshb0b00ac0dffbafds"document.location = "/settings/profile?_method=PUT&" + form_data + "&" + token + "=" + param;
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
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:
class SettingsController < ApplicationControllerdef show@user = User.find(session[:user_id])enddef update@user = User.find(session[:user_id])@user.update_attributes(params[:settings])endend
Security::Application.routes.draw domatch 'show_settings' => "settings#show"match 'update_settings' => "settings#update"end
Your application is now totally open to CSRF attacks. I can redirect a user to
/firstname.lastname@example.org 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:
Security::Application.routes.draw doget 'show_settings' => "settings#show"put 'update_settings' => "settings#update"end
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:
Security::Application.routes.draw doresource :settingsend
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.
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…