Friday 10 February 2012

XSS Protection in Rails 3


One of the places where web applications are vulnerable to XSS attacks is when they display input from users. It is therefore important to escape any user-generated input that is displayed and in Rails applications this is usually done by making use of the h method.
  1. <%= h comment.content %>  
Using the h method to escape output.
In Rails 3, however, output is escaped automatically so there’s no need to put the h method in your views and in this episode we’ll show you how Rails 3 handles the escaping of output.
To demonstrate output escaping we’re going to use a simple blogging application written in Rails 3. In this app we have articles and each article has a number of comments associated with it. To test how the comment system copes with an attempt at cross-site scripting we’ll enter <script>alert('I steal cookies!')</script> in each field in the comments form and submit our evil comment.
Entering an evil comment.
When we add the comment and it is displayed on the page we’ll see that Rails 3 has automatically escaped the HTML tags in the fields that we submitted. Let’s take a look at how Rails does this.
The HTML comments are automatically escaped.
The code that displays each comment is contained in a partial and if you look at the code you’ll see that none of the output is escaped with h.
/app/views/comments/_comment.html.erb
  1. <div class="comment">  
  2.   <strong><%= link_to comment.name, comment.url %></strong>  
  3.   <p><%= comment.content %></p>  
  4. </div>  
In Rails 2 this would have meant that the alert messages would have been shown, but all of the output in the partial is being correctly escaped by Rails 3, even when passed through helper methods such as link_to so there’s no need to use the h method here.
What happens, thought, if we’re transitioning an application from Rails 2 and have our output escaped with h? We can find out by escaping the output in the partial above and reloading the page.
/app/views/comments/_comment.html.erb
  1. <div class="comment">  
  2.   <strong><%= link_to h(comment.name), comment.url %></strong>  
  3.   <p><%= h comment.content %></p>  
  4. </div>  
When we reload the page we’ll see that it looks the same and the output hasn’t been double-escaped. Rails is clever here; even if we use the h method it will escape the script tag only the once.
It might seem that the h method does nothing in Rails 3 but that’s not that case. It does still have a purpose and we’ll show you later what that is. But before we do that we’ll look at a related feature. While it’s great to have the output escaped automatically there might be times when we want to display the raw content. If we trust the content that the user enters, say they’re an administrative user, and we want to display exactly what they enter then we can use the new raw method to do that.
  1. <div class="comment">  
  2.   <strong><%= link_to comment.name, comment.url %></strong>  
  3.   <p><%= raw comment.content %></p>  
  4. </div>  
If we reload the page now the JavaScript we entered in the comment field will be executed.
Using the raw method means that the alert is shown.
So, in Rails 3 whenever we don’t want content to be HTML escaped we can use the raw method. But how does it work? Rails 3 seems to be pretty clever about when to escape content and when not to. We’ll show you now how that works.
We’ll demonstrate escaping in the console, which in Rails 3 we can invoke with the rails c command.
$ rails c
Loading development environment (Rails 3.0.0.beta)
ruby-1.9.1-p378 >
Rails 3 has a concept of HTML-safe strings. This means that we can check if any string is safe to output as HTML by calling the new method html_safe? on it.
> "foo".html_safe?
 => false 
We can mark a string as being HTML-safe by calling the html_safe method on it.
> safe = "safe".html_safe
 => "safe" 
> safe.html_safe?
 => true 
No actual escaping goes on here. All that happens is that a boolean property is set against a string to determine whether it should be escaped before being output.
So how does this apply in our view template? Well, Rails looks at each piece of output and sees if it’s marked as HTML-safe. If it’s not then it will be automatically escaped when it is output to the view. If it is safe then it is passed through without being escaped. If we use the h method to escape a string it will perform the escaping and mark the string as HTML-safe. This means that Rails 3 will see that the string is safe and not escape it again.
When the raw method is used on a string it will be marked as HTML-safe but not escaped, ensuring that the string’s content is passed to the output without being changed.
This is an important thing to understand when you’re using helper methods. We’ll explain this by creating a helper method called strong that wraps whatever is passed to it in <strong> tags. We’ll use it in our template like this:
/app/views/comments/_comment.html.erb
  1. <div class="comment">  
  2.   <%= strong link_to(comment.name, comment.url) %>  
  3.   <p><%= raw comment.content %></p>  
  4. </div>  
We’ll create the strong method in the ApplicationHelper:
/app/helpers/application_helper.rb
  1. module ApplicationHelper  
  2.   def strong(content)  
  3.     "<strong>#{content}</strong>"  
  4.   end  
  5. end  
When we reload the page, however, we’ll see that this hasn’t worked the way we wanted it to.
The strong tag in our helper method is escaped.
Rails 3’s automatic escaping has escaped the <strong> tag here and this is because our helper method doesn’t create an HTML-safe string.
There are two simple rules that need to be need to be followed when working with helper methods that return HTML. Firstly, we need to ensure that any strings returned are marked as HTML-safe.
/app/helpers/application_helper.rb
  1. module ApplicationHelper  
  2.   def strong(content)  
  3.     "<strong>#{content}</strong>".html_safe  
  4.   end  
  5. end  
This fixes the problem of the <strong> tag being escaped but by doing that the content between the tags will not be escaped. We can solve this by escaping the content with h:
/app/helpers/application_helper.rb
  1. module ApplicationHelper  
  2.   def strong(content)  
  3.     "<strong>#{h(content)}</strong>".html_safe  
  4.   end  
  5. end  
So as long as we remember to escape all input with h and then mark the resulting string as html_safe it will be rendered properly. If we reload our comments page now we’ll see that the <strong> tag hasn’t been escaped but that the content of the second comment, which includes the potentially dangerous JavaScript is escaped.
The helper method is now escaped correctly.
That’s it for this episode. The automatic escaping is a welcome addition to Rails 3’s views and it removes the need to remember to escape every piece of output with h, this reducing the changes of your application being a victim of cross-site scripting.

No comments:

Post a Comment