Wednesday, April 7, 2010

JSON-Enabling your Web-Application: Tread. Very. Carefully.

None of what follows is new. Many issues below stem from recurring tempting circumvention techniques of cross-site data-loading security policies that were put in place by browser vendors a very long time ago, for very good reasons.

So you've created a very elegant and easy to maintain Web Application, perhaps leveraging some MVC patterns.

Your Controllers may be mapped to URLs such as these:
  • FavoritesController: list() and view(int id) methods:

    1. /favorites/list
    2. /favorites/view/1
    3. /favorites/view/2
    4. ...
  • SomeOtherControllers following similar patterns
Your web-based application works pretty well and features a decent amount of no-nonsense better-practices among which you might find: user account creation with e-mail verification and CAPTCHA, md5-hashed password authentication against a large salt unique to each user, it's devoid of very obvious SQL-Injection vulnerabilities because you're using a decent database abstraction layer, you're not allowing users to post unchecked input so you're not feeling too vulnerable to obvious XSS Attacks, you've avoided using non-idempotent RESTful URLs so you're less vulnerable to insanely obvious CSRF Attacks, but you're clearly not stopping there, by requiring all transactional HTTP POSTs to send a non-guessable and unique session-lived token as a parameter.

At this point you're pretty confident that a determined cracker will actually have to do some work to mess with your application to uncover any sensitive data it may have.

Then you get ready to Go Mobile. It only makes sense: you have a compelling application and you wish to offer your users a compelling extension of your application to their Mobile Universe, while creating additional revenue opportunities. Everybody wins.

As a design team starts putting together mock-ups of the handheld experience, which may take quite a few weeks, you realize that regardless of what they come-up with, the handheld application will almost certainly need to exchange data with the web-based application. So you figure you might as well start exposing web services to cater to various use-cases.

Your MVC stack looks neat, and now just plain screams for exposing a JSON interface to most of your Controllers. It only makes sense. Once you're done, you can send the API to the Objective-C/Java Developer who'll be building your mobile apps for Blackberries, iPhones and Droids.

So you rig your controllers to become "json-aware": instead of only being able to output HTML, you're now also able to send a JSON payload:
  • FavoritesController: list() and view(int id) methods:
    1. /favorites/list/json
    2. /favorites/view/1/json
    3. /favorites/view/2/json
    4. ...
Before doing any of this, STOP and realize this:

JSON is valid JavaScript. Valid JavaScript can be loaded in a good ol' HTML script element through any site on the Web directly from your site. Whatever your script returns, such as a list of an authenticated user's favorites, is more or less fully-accessible to any other script running on that foreign document.

Forget your Handheld/Mobile project for a second as this isn't the vector of the gaping vulnerability you've just created:

Consider http://evilsite/somebadwebpage.html with the following line of code: <script src="http://yoursite/favorites/list/json"></script>

If evilsite manages to lure some of your users, perhaps thru some crafty social-engineering sprinkled with obfuscation, they're now in a position to capture a victim's favorites.

If you're going the JSON route, you might consider the following simple techniques:
  • Require a completely separate authentication handshake for your JSON services, they should not function if a user happens to be "authenticated on the site" while browsing from their desktop.
  • Wrap your JSON payload in a light XML enclosure: it'll be enough to make browsers barf on a JavaScript parse error should someone try to reference your JSON URI in their web document.

    Don't go loading a full XML/DOM parser, just substring/indexOf/lastIndexof your way to the JSON payload.