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

Share this article

This is the final part of our exploration into the Rails security. Last week we looked at mass-assignment and SQL injection issues, having previously explored the risks of session hijacking. This week we’ll look at risks to caching, protecting against XSS, and a couple of other Rails security tips.

The risk of caching

Fragment caching well is one of the most important things you can do for performance. Nested fragment caches that gracefully expire are powerful and beautiful, but there is a big risks attached: how do you make sure that people only see things that they are meant to see? This has caught me out on a few occasions and can be horrible to track down.

The big danger is embedding user-specific code in templates that end up cached. I’ll take a real world example that nearly caught us out. On Meducation a user has a profile with various information about them. We used to have a section on a profile page, which was only visible to administrators, that displayed the user’s email address and other information – mainly in case we needed to contact them. We had a template like this:

[gist id=”2990609″]

…with a partial like this:

[gist id=”2990613″]

The functionality here is pretty standard and while not particularly elegant it’s pretty safe. The problem occurred when we decided to add caching into show.html.erb:

[gist id=”2990615″]

This nice nested caching improves the performance of our app, but suddenly we’ve introduced a massive security hole: our admin-only code is getting cached. Depending on which user renders this code first, either everyone will see the admin code, or no-one will see the admin code. Both of these scenarios are pretty bad.

The problem here is that we didn’t take into consideration the user that is viewing the data, only the data that is being rendered. If your code contains any reference to current_user, you need to be very careful.. There’s no magic bullet to solve this issue, you just have to be very aware and very careful when caching data. The rule I generally use is that if there are only one or two calls to current_user methods, and the calls are ? methods, then I’ll include them in the cache key, otherwise I won’t cache it (and generally decide this is a code smell and refactor the view!). In this scenario, I’d alter the code to:

[gist id=”2990616″]

As a final thought on this, in real code you want to be extracting these cache keys into helper methods. The code above is pretty difficult to read and hard to maintain.

Protecting against XSS (Cross Site Scripting) Attacks

XSS attacks are some of the most dangerous attacks you will be faced with. Rails 3 protects us against basic XSS, but as always, it’s easy to wreck Rails’ protection if you’re not careful.

XSS attacks involve a malicious user injecting code that your application later renders. Let’s take a basic example:

Let’s say you have site that hosts blogs. Users can create posts with a title and some content. Your show.html.erb looks something like this:

[gist id=”2988949″]

Now, last week someone posted a blog post on your site that sad bad things about me. I’ve decided to take it out on you by causing some chaos. To do this, I create a new blog post, but rather than giving you some interesting information about what coffee I’ve been drinking today, I enter some HTML containing Javascript. For example:

[gist id=”2988939″]

In Rails 2, your view would render your show template as:

[gist id=”2988954″]

This was clearly VERY bad. The alert box is annoying, but if I wanted to I could write JavaScript that hijacked sessions, posts forms using the one-time authenticity_token, or do all sorts of other real damage.

As you’ve probably guessed, Rails 3 added protection to stop this, so that the template will now be rendered like this:

[gist id=”2989002″]

What’s the problem then, you ask?

Well, there are a two big problems. One catches out the unwary, the second can catch out an experience developer.

The first problem is a simple one. What happens if you want to allow the user to enter some HTML, such as bold, italics, etc? Lots of users at this stage simply decide to stop escaping HTML and use the raw keyword, e.g.:

[gist id=”2989057″]

Suddenly, you have undone all of Rails’ hard work and chaos can rein. NEVER use raw on any data that contains anything that a user has inputted. This applies to normal text, image srcs, css, anything. Even if data just contains interpolated user input, don’t risk using raw.

If you want to allow HTML tags in your views, then use the Rails provides the sanitize method. For your blog, you would change your show.html.erb to look like this:

[gist id=”2990637″]

Alternatively, use Markdown. RedCarpet provides a simple, robust solution, and is often easier for users than trying to write in HTML. If the content your users is writing is technical, then consider using Github’s markdown.

The second, slightly more complex example is when you build a helper method. Let’s say we want to build some functionality that wraps some data in a table:

[gist id=”2990648″]

To get Rails to output this content out as HTML, we have used the html_safe method. This marks the content as a “Safe Buffer”, which Rails can output without escaping. However, in doing this, we have also removed the escaping protection from the data and we are again vulnerable to XSS.

Our first attempt to change this make this safe is to directly escape the user’s data using h. For example:

[gist id=”2990661″]

However, this code is still cumbersome, error-prone and difficult to maintain. Building up content using string concatenation in this way in Rails should be avoided. Use tag and content_tag instead, which handle the sanitation of their content directly. Now we can change out method to this:

[gist id=”2990816″]

This code uses safe_join. You cannot use normal join as it joins the output with strings, not a SafeBuffer, so you have to respecify html_safe. An alternative that I quite like is to use reduce:
[gist id=”2990753″]

Avoiding XSS is not complex – simply take care when working with external data.

A couple of tips.

There are three final things I want to mention.

Firstly is with regards to logging. Be careful what is stored in your logs. Logs are just text files with no security, and if they get into the wild, and contain sensitive information, you could be in big trouble. By default, Rails strips passwords from your logs. You can also strip other sensitive data, such as email addresses very easily. In your config.rb, simply edit the filter parameters line as follows:

[gist id=”2992458″]

Secondly, denial of service attacks are becoming increasingly common. It’s impossible to totally protect against them, but it is possible to reduce the risk by moving everything as asynchronously as possible. Keeping your request lifecycle as short as possible is vital both for performance, and as a safety net against DOS attacks. Move anything you can to asynchronous tasks via a method such as delayed_job.

Finally, consider security to be like an onion. Add as many layers as you can, in the knowledge that some will likely be penetrated, but that you can keep the core safe. Watch your traffic and logs carefully looking for possible attacks and monitor exceptions to see what hackers are trying to do. Keep improving your security and auditing your application and keep your gems fresh and up-to-date.

Don’t Trust Data!

The theme of this series has been a simple one: don’t trust data. Don’t trust that the data is from whom you think it is, don’t trust what the data might do to your application, and don’t trust what might happen when you render it. Remember this rule and you’ll be well on your way to writing safer apps. Good luck!

Jeremy WalkerJeremy Walker
View Author

Jeremy Walker has been using Rails since 2005. He is the CTO of Meducation - an educational social-network for medics - and runs his own software consultancy. He is a maintainer of various open source projects, including Propono, Larva and Inqusitio. You can follow Jeremy on Github and Twitter, and read more about him at his website.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week