JSON / JSONP XSS vulnerability with AngularJS and Rails

In reading the AngularJS documentation for the $http service, there is a very clear warning about a potential JSON/JSONP vulnerability.  The short version of this is that JSONP was introduced as a technique to build mashups and composite applications, it allows one domain to call services on another domain.

The problem is that JSON is considered syntactically valid JSONP.  So a service that you built as JSON can be called using a JSONP tag.  Further, through some esoteric magic, it is possible for one web site to submit a request to another website using your already signed in session, and then gain access to the response method from javascript on the first site.  In other words, for that first site to have script embedded in it that does things with your logged in session on another website.

The outline description of this can be found on the AngularJS $http page, in the security considerations section.

The CSRF vulnerability mentioned on that page is dealt with in this post on this blog.  This post provides my current solution to the JSON/JSONP issue.

The vulnerability appears to come about when an array is returned without being encapsulated in a json object.  So

  [{name: "Fred", age: 10, height: 120},{name: "Ahmed", age: 18, height: 178}]

is vulnerable to hijacking.  But if we were to use the standard rails response format, which responds with an object that contains the array (known as including the root node), then the vulnerability wouldn’t occur.

  {users: [{name: "Fred", age: 10, height: 120},{name: "Ahmed", age: 18, height: 178}]}

This was the standard approach in Rails 3.0.x, but in Rails 3.1.x the default response format is to return the bare array, and the AngularJS $Resource expects to integrate with a bare array.

The recommended AngularJS solution to the json vulnerability is to modify all json responses to prepend:

  )]}',

on the front of every response.  So our example would become:

  )]}',
  [{name: "Fred", age: 10, height: 120},{name: "Ahmed", age: 18, height: 178}]

My understanding of why this works is that all known exploits of the JSON/JSONP vulnerability involve embedding the request into a <script> tag, and putting code in front of the request so that your browser is actually getting something like:

  <script>sendToEvilSnooper([{name: "Fred", age: 10, height: 120},{name: "Ahmed", age: 18, height: 178}])</script>

By putting the closures on the front of the array the Angular team believe that they’ve made it impossible to get a syntactically valid javascript command – so your browser is now going to get:

  <script>sendToEvilSnooper()]}',
  [{name: "Fred", age: 10, height: 120},{name: "Ahmed", age: 18, height: 178}])</script>

And it’s going to return an error or otherwise fail to send this data off to evil snooper land.  Facebook and some other sites use other variants of this approach, such as prepending the response with while (1){}, which will send the browser into an endless loop and fail to execute anything beyond that point in the code.

So much for the theory, the next question is how we apply this to our AngularJS and Rails application.  I have considered and discarding the option of turning back on the root node.  Firstly I’m not 100% confident that it closes all the possible security holes, secondly I want to use the native AngularJS $resource, and anything that closed this hole would mean replacing or otherwise overriding that service.  So I’m looking for a solution that puts the ([{‘, on the front of every json response.

Ideally this solution would be something I could do centrally – a configuration option in Rails that said “please prepend this to every response”.  I’ve looked at some options, however all of them require some form of template for each action of each controller – so for example in the views directory we might have views/clubs/index.json, views/clubs/show.json etc.  I posted a question on stackoverflow looking for a solution, to which I did not receive any answers.

Having thought about it further, my current approach has been to return every item from the object in the json response.  But over time I might prefer to return only a subset – perhaps I’d hide the timestamps or some other information that I don’t want manipulated on the client side.  So I can justify to myself that I create templates for each controller.  But right at the moment I don’t need that, so I’m still keen to find a way to do this centrally and easily.  The approach I’ve come up with is to create a render helper method in the application_controller.rb, and to then use that in my controllers.

Changes to app/controllers/application_controller.rb are to create the “render_with_protection” method:

class ApplicationController < ActionController::Base
  protect_from_forgery

  after_filter  :set_csrf_cookie_for_ng

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

  def render_with_protection(json_content)
    render content_type: 'application/json', text: ")]}',\n" + json_content
  end

protected

  def verified_request?
    super || form_authenticity_token == request.headers['X_XSRF_TOKEN']
  end  
end

In each of the controllers we can then use this as follows:

  # GET /clubs.json
  def index
    @clubs = Club.all

    render_with_protection @clubs.to_json
  end

AngularJS seems happy to parse and use this.  This content is included in both the Rails 3 and Rails 4 tutorials that are available from the menu above, the Rails 4 tutorial page that specifically does this is here.

Advertisements

4 thoughts on “JSON / JSONP XSS vulnerability with AngularJS and Rails

  1. Pingback: AngularJS and Rails CRUD application using ng-boilerplate and twitter bootstrap: Tutorial Index | technpol

  2. Pingback: Rails app for Angularjs, building the rails league application: part 1 | technpol

  3. Pingback: 6: Adding ngGrid, and edit page, and CSRF/JSONP protection | technpol

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s