Techniques to Secure Your Website with Ruby On Rails (Part 3)
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:
…with a partial like this:
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
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:
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:
In Rails 2, your view would render your show template as:
As you’ve probably guessed, Rails 3 added protection to stop this, so that the template will now be rendered like this:
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.:
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
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:
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:
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:
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
content_tag instead, which handle the sanitation of their content directly. Now we can change out method to this:
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
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:
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!