Bad coding style can lead to XSS in Ruby on Rails

Last year (around the 20th of October), Sebastian was working on a project in Ruby on Rails. While writing some really dirty code, he noticed that it’s possible to run into XSS issues by nesting rails’ form helpers.

Ruby on Rails does a lot to mitigate common security issues. The ORM makes SQL Injections nearly impossible and the templating system takes care of XSS attacks. Additionally, rails offers a lot of convenient functions to create HTML data. For example, you can use the following snippet to create an icon which links to a specific action/contronller:

link_to (content_tag(:i,'', :class => 'action-icon icon-trash')), controller_path(@controller), method: :patch, name: "dofoo"

However, while playing around with different nestings of different form helpers, something strange happened. While trying to create a button with an icon in it (pretty strange idea ;)), the html output did not look as expected:

button_to (content_tag(:i,'', :class => 'action-icon icon-trash')), {param: id }, method: :put

This generated the following HTML code:

<input type="submit" value="<i class=" action-icon="" icon-trash"="">" />

As you can see, rails tries to escape some output, but the <i class=""....> is not encoded properly, breaking the HTML code of the value-attribute. What we would have expected is this:

<input type="submit" value="<i class="action-icon icon-trash"></i>"/>

The reason for this misbehaviour is the following, (quoting Mr. Koziarski from the Rails security team):

The reason the code you provided is producing broken HTML like that is that content_tag returns a String which has been marked as html_safe?, and safe strings are output directly, not escaped.
So at first glance the code in button_to is functioning as intended, but the net result is certainly pretty messed up.

The entire XSS protection code is built around the assumption that an attacker can never generate html_safe strings, and that the only thing which marks those things as safe is either a rails helper (which escapes everything internally) or user data which has been passed through an escaping function (sanitize or html_escape).

After taking another look at this issue, we managed to find an attack scenario. Passing user-input as the class-attribute does the trick:

  =button_to (content_tag(:i,'', :class => params[:test])), {param: 1 }, method: :put

Opening the url with the user-supplied test-parameter: “?test=onmouseover=alert(1)//” leads to the following HTML output. Moving the mouse over the input-field will trigger the XSS:

<input type="submit" value="<i class=" onmouseover="alert(1)//"">

As initially mentioned, this kind of attack would require a developer to write bad code, so the security team decided to not care much about this issue.

It seems unlikely that this code would be deployed in production so it’s not something that I think we should cater for. Especially given the difficulties escaping the data provided, as you point out html_escape is idempotent and won’t escape already-safe strings.

This has been tested with:

- Ruby 2.0.0
- Rails 4.0
- Chromium-Browser 
- Puma as rails-server

It was a pleasure to communicate with the Rails Security Team and we’d like to thank them for taking their time and their explanations. It seems like this “bug” will stay unfixed. So take care of your coding style :P

The team of