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.

Advertisements

Action and reaction: event-driven AJAX updates with jQuery

Posted in code, jquery by mtjhax on September 7, 2010

Newton's CradleAs I increasingly use AJAX to let users perform actions in a page without navigating away, I find myself constantly having to solve the problem of updating page content that depends on these actions. For example, a user logs in with a popup bubble, then parts of the page that depend on login state must be updated. This is a relatively trivial example and there are many approaches to handling this sort of update.

A straightforward method that is commonly used is to avoid AJAX in these cases–submit the login form and redirect back to refresh the entire page. While using AJAX sparingly is a worthwhile goal, avoiding it entirely doesn’t always result in the most engaging user experience. Another approach is to hard-code everything that needs to be updated after an action is complete:

// submit my login form with jQuery .ajax()
$.ajax({ url: login_url, data: login_data, complete: function() {
  // update page contents that depend on login state
  $('#mainmenu').load('mainmenu');
  $('#login_form').fadeOut('slow', function() {$(this).remove()});
  $('.welcome_text').show();
});

Fine for one or two actions, but this turns into a spaghetti mess pretty quickly in more complicated situations. A better solution is to have some way of registering update callbacks, e.g.:

$('#mainmenu').updateOnEvents('login', function() {
  $(this).load('mainmenu');
});

$('#login_form').updateOnEvents('login', function() {
  $(this).fadeOut('slow', function() {$(this).remove()});
});

$('.welcome_text').updateOnEvents('login', function(){
  $(this).show();
});

$.ajax({ url: login_url, data: login_data, complete: function() {
  // globally trigger the event 'login' somehow
});

So the question becomes, how do you globally trigger the event so any element at any level can register a handler? After considering a number of designs involving .live() or .delegate() (so the handler setups would affect both existing elements and new elements added later) I realized that good old .bind() would do the trick.

Solutions involving .live() and .delegate() are tricky because when you use something like $('.foo').live() or $(document).delegate('.foo'), you need to use $('.foo').trigger() for the event to be handled. Calling something higher-level like $(document).trigger() does not invoke the handlers. The trick is to roll your simple .live()-style function similar to the following:

$.fn.updateOnEvents = function(events, callback) {
  $(document).bind(
    events,
    { selector: $(this).selector, context: $(this).context },
    function(event, trigger_data) {
      $(event.data.selector, event.data.context).each(function(){
        if (typeof callback == 'function') {
          extra_data = callback(event, trigger_data);
        }
      });
    }
  );
};

// example usage
$(document).ready(function() {
  $('.welcome_text').updateOnEvents('login logout', function(event, data){
    $(this).text(data.message).show();
  });

  $(document).trigger('login', { message: "Welcome back!" });
  $(document).trigger('logout', { message: "See you next time." });
});

The trick is that when we call $('.foo').updateOnEvent(), the selector string ‘.foo’ is saved (and the context, if specified) and are passed to the handler function in event.data. The handler uses the selector and invokes the callback function for each matching element with .each(), so the value of $(this) in your callback is the element itself instead of $(document). Since the selector is evaluated at the time of the callback, any recently-added elements that match are included in the update.

The parameter trigger_data is whatever extra data you pass with the .trigger() call and your callback can take advantage of that data.

Commonly-used patterns can be expressed as a shortcut in the code. For example, in this variant if the callback parameter is a string instead of a function, the code treats the string as an AJAX URL and assumes you want to update the selected elements with .load():

$.fn.updateOnEvents = function(events, callback) {
  $(document).bind(
    events,
    { selector: $(this).selector, context: $(this).context },
    function(event, trigger_data) {
      if (typeof callback == 'function') {
        $(event.data.selector, event.data.context).each(function(){
          extra_data = callback(event, trigger_data);
        });
      } else if (typeof callback == 'string') {
        $(event.data.selector, event.data.context).load(callback, trigger_data);
        });
      }
    }
  );
};

// slightly absurd example of using AJAX to retrieve a welcome message
$(document).ready(function() {
  $('.welcome_text').updateOnEvents('login logout', '/ajax/welcome_msg');
  $(document).trigger('login', { type: "login" });
  $(document).trigger('logout', { type: "logout" });
});

In yet another variant where I always want to update the element with AJAX via .load(), instead of passing a callback function to perform the updates, I passed a callback function that returns the parameters for the .load() call — in this way, the .trigger() function doesn’t need to know anything about the parameters needed by the update callbacks:

$.fn.updateOnEvents = function(events, ajax_url, params_callback) {
  $(document).bind(
    events,
    { selector: $(this).selector, context: $(this).context, url: ajax_url },
    function(event) {
      var data = extra_data;
      $(event.data.selector, event.data.context).each(function(){
        // get AJAX request parameters from callback function
        if (typeof params_callback == 'function') {
          data = params_callback(event);
        }
        // convert params to string so .load() always uses GET method
        if (typeof data == 'object') {
          var new_params = "";
          for (var key in data) {
            if (new_params.length > 0) new_params += "&";
            new_params += key + "=" + data[key];
          }
          data = new_params;
        }
        // update the element with AJAX
        $(this).load(e.data.ajax_url, data);
      });
    }
  );
};

// another slightly ludicrous usage example
$(document).ready(function() {
  $('.welcome_text').updateOnEvents('login logout', '/ajax/welcome_msg', function(event){
    if (event.type == 'login')
      return { type: "login" };
    else
      return { type: "logout" };
  });
});

I have to profess that I am not a jQuery god (yet). I am certain that there are improvements that could be made to this function in terms of performance and simplicity, maybe a potential error or two, or a completely better way to approach the problem. I welcome your suggestions, comments, and criticism!

Tagged with: , , , , ,