First, my apologies for really dropping my commitment to this blog the last six months. The next six months will be better, I promise.

Second, the presenter pattern we built into our latest Ruby on Rails and Backbone.js application created an opportunity for XSS attacks.

XSS attacks are nasty little security holes exploited through the DOM, although not common for casual users to hack, experienced hackers can cause a ruckus on your application if found. On our application we found the security hole in an authenticated area, which is better, none the less there are probably hundreds of applications using Backbone.js where this vulnerability is serious.

In a Rails application it is possible/common to setup presenters to ready your API data consumed by a client(iPhone, Backbone.js, Android…etc). We abandoned RABL and format.json for an html.erb solution to populate our Backbone routers. When I first started using Backbone.js, mostly for play projects, I wouldn’t have considered this model. Rails controllers allow the formatting of JSON quickly and easily. We had different requirements for our pages where we still wanted html.erb templates to render our data. So to deliver data to our Backbone models we setup a Rails view template like this:

1
2
3
4
5
6
7
8
9
10
#app/views/some_model/show.html.erb
 
<script type="text/javascript">
  $(function() {
    window.router = new Mgk.Routers.ProfileRouter({
      contactDetails: <%= @contact_details.to_json.html_safe %>, 
      creditCards:    <%= @credit_cards.to_json.html_safe %>});
    Backbone.history.start();
  });
</script>

This is a really easy way to deliver data to your application, but it’s based on one cockamamie assumption. Your data objects from the server must be safe for browser consumption – not chill. It is really easy to think you can validate input data for certain terms to keep your output data and system safe, but one would be wrong to even try and evaluate all the possibilities.

Because the html.erb setup allows us to render data outside of Backbone context (like an i18n body of text, or anything that doesn’t need to be dynamic), we want to pass the JSON objects here in this case – at least for the near future. The issue we need to address is the “html_safe” method at the end of our Ruby interpolation. If the “html_safe” is removed the entire JSON hash is encoded making it impossible for the Backbone router and model to understand what we are sending.

Backbone templates (Eco, Handlebars) do a great job of escaping the data they get before rendering it on the page, but those templates don’t even have the data before the attack occurs. The timeline is like this.

1) Example HTML Page: User changes name and enters the following in a form:

<script>alert(123456)</script>

2) Rails does validation
3) Data passes validation
4) Data stored
5) Request for Example HTML page comes from browser
6) Rails serves the following in the .erb template:

  "<script>alert(123456)</script>".html_safe

7) Browser either passes an exception and breaks, or it renders the alert box
8) You Die, try again

How to fix this?

We need to sanitize the values in our JSON hashes, but none of the other characters or keys. This needs to happen before we get to the .erb template. Because we are using the Presenter Pattern, the first example solves it there, the second solution is less straight forward.

1
2
3
4
5
6
7
8
9
10
11
  # before
  # app/controllers/some_models_controller.rb
 
  def show
    respond_to do |format|
      format.html do
        @contact_details = ContactDetails.from(current_user)
        @credit_cards = current_user.credit_cards
      end 
    end 
  end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# before
# Presenter for a user's contact details
class ContactDetails < Hash
  RELEVANT_ATTRIBUTES = [:first_name, :last_name, :login_email, :login_cell_number]
 
  RELEVANT_ATTRIBUTES.each do |a| 
    define_method(a) { self[a] }
    define_method("#{a}?") { !!self[a] }
  end 
 
  def self.from(user)
    self[user.attributes.symbolize_keys.slice(*RELEVANT_ATTRIBUTES)]
  end 
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# after
# Presenter for a user's contact details
# app/models/contact_details.rb
 
class ContactDetails < Hash
  RELEVANT_ATTRIBUTES = [:first_name, :last_name, :login_email, :login_cell_number]
 
  RELEVANT_ATTRIBUTES.each do |a| 
    define_method(a) { self[a] }
    define_method("#{a}?") { !!self[a] }
  end 
 
  def self.from(user)
    a = self[user.attributes.symbolize_keys.slice(*RELEVANT_ATTRIBUTES)]
    a.each {|k,v| a.merge!(k => CGI::escapeHTML(v)) }
  end 
end

That wasn’t too hard. Merge escaped values into the existing hash. We do essentially the same thing for associated models, but this time without single presenter. We don’t have a specific credit card presenter or user presenter.

1
2
3
4
5
6
7
8
9
10
11
12
# before
# app/models/user.rb
 
class User < ActiveRecord::Base
 
  "..."
 
  has_many :credit_cards
 
  "..."
 
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# after
# app/models/user.rb
 
class User < ActiveRecord::Base
 
  "..."
 
  has_many :credit_cards do
    def escaped
      cards = []
      self.each do |cc|
        a = cc.attributes
        a.each {|k,v| 
          if v.is_a?(String) then a.merge!(k => CGI::escapeHTML(v)) end 
        }   
        cards.push(a)
      end   
      cards
    end 
  end
 
  "..."
 
end

Lastly, update your Rails controller current_user.credit_cards with escaped

1
2
3
4
5
6
7
8
9
10
11
  # after
  # app/controllers/some_models_controller.rb
 
  def show
    respond_to do |format|
      format.html do
        @contact_details = ContactDetails.from(current_user)
        @credit_cards = current_user.credit_cards.escaped
      end 
    end 
  end

Backbone is very new and there are many ways to setup your application, but be very careful in knowing what you are serving into client, and where it is rendered along the way.

Leave a Reply

Your email address will not be published. Required fields are marked *