MTJ Hax

Backboned

Posted in code, javascript by mtjhax on February 26, 2013

Read Backbone’s docs carefully to avoid a fatal security flaw

If you use Backbone.js you are probably “bootstrapping” the data for your models, per their instructions:

Loading Bootstrapped Models
When your app first loads, it’s common to have a set of initial models that you know you’re going to need, in order to render the page. Instead of firing an extra AJAX request to fetch them, a nicer pattern is to have their data already bootstrapped into the page. You can then use reset to populate your collections with the initial data. At DocumentCloud, in the ERB template for the workspace, we do something along these lines:

<script>
  var accounts = new Backbone.Collection;
  accounts.reset(<%= @accounts.to_json %>);
  var projects = new Backbone.Collection;
  projects.reset(<%= @projects.to_json(:collaborators => true) %>);
</script>

 
You have to escape </ within the JSON string, to prevent javascript injection attacks

If you are using Rails 3.x and follow the example code verbatim, you might get something like this in your output:

// var widget = new widgets.model(<%= @widget.to_json %>)
var widget = new widgets.model({ &quot;title&quot; : &quot;&lt;/script&gt;&lt;script&gt;alert('hi')&lt;/script&gt;&quot; });

Obviously, the output is being escaped and .html_safe or raw() needs to be used:

// var widget = new widgets.model(<%= @widget.to_json.html_safe %>)
var widget = new widgets.model({ "title" : "</script><script>alert('hi')</script>" });

Now wait just one second! Some sneaky devil has entered some JavaScript in their title field. You might wonder if this is okay because it’s inside a string — it’s not. If you run this example the alert("hi") code will be executed because the browser’s HTML parser can’t sully itself with understanding JavaScript syntax, so closes the script tag at the very first </script it sees. So by unescaping your output you have created a wide-open security hole exposing your users to XSS attacks.

Maybe you didn’t notice that last line in Bootstrap’s instructions. It’s pretty important.

You have to escape </ within the JSON string, to prevent javascript injection attacks.

I almost fell for this trap before remembering how dangerous html_safe or raw() could be in Rails views, especially in a script section. An old-school trick for breaking up or escaping </script> inside strings is to use "</sc"+"ript>". Ugly hack. There’s an ugly but non-hack way to do it with a CDATA directive. The accepted method, per Backbone’s link, is to simply escape the forward slash character with a backslash: <\/script>.

So you might ask, what is the proper way to do this in Rails? John Firebaugh has a nice writeup of the various alternatives. Some developers have suggested monkeypatching the to_json but this hides the protection from the view and can lead to bad habits. I believe the view code should show explicit evidence of escaping to prevent injection attacks until such escaping becomes a Rails default. Firebaugh ended up suggesting that you replace ActionView::Base#json_escape instead, simply escaping all forward slash characters with blackslashes (gsub('/', '\/')). This is a safe approach because the use of json_escape in views makes the protection explicit.

If you prefer not to overwrite Rails methods to accomplish this task, you could just define a new method such as Object#to_safe_json or ActionView::Base#safe_json. Another way is to use escape_javascript and html_safe together to create a string which is then parsed in JavaScript:

//$.parseJSON('< %= escape_javascript @widget.to_json.html_safe % >')
var widget = new widgets.model($.parseJSON('{\"title\":\"<\/script><script>alert(\'hi\')<\/script>\"}'));

Some developers feel this is inefficient because you have to parse the JSON string, but it makes sense to me — you parse the JSON string returned by every AJAX request, so why not the initial one? This shouldn’t affect your page load performance unless you are bootstrapping a huge Collection, in which case you might want to think about adding some pagination.