2009-04-19

JSONP and XSS

Allen Varney asks:
...doesn't this functionality leave the map site open to -- at the very least -- cross-site scripting (XSS) hacks? The whole procedure sounds quite insecure.
Short answer: no (but read on)

But it's an excellent question, so let's analyze the potential security vulnerability.

First off, Wikipedia has a reasonable Cross Site Scripting (XSS) overview.

The short version is that a vulnerability in some site allows an hacker to add content that, when viewed by a victim, executes the hacker's code using the victim's data.

For example, let's say VulnerableForum.com allows evil hacker Eve to post arbitrary script code. When victim Alice visits the site, the web page loads and Eve's code can execute within the context of VulnerableForum.com. That means it could do things like make forum posts in Alice's name, change Alice's password, modify Alice's profile (perhaps to include additional copies of Eve's code, turning this into a viral worm), and so forth.

The Wikipedia article lists several variations of this beyond simple persistent content modification, e.g. Eve sends Alice a URL to VunerableForum.com that includes parameters which induce bugs in the forum software, allowing code injection.

The "classic" form of XSS was what led to the Same Origin Policy (SOP). In the old days, Eve's web site (EvilHacker.com) could load up AlicesBank.com in an IFRAME. If Alice visited EvilHacker.com, the IFRAME would load AlicesBank.com - and possibly log in automatically. The scripts from EvilHacker.com could then reach into the IFRAME and manipulate the now-authenticated bank pages to steal money. In response to this, browser makers implemented the SOP, which means that the scripts can't reach into the IFRAME if the content is from a different site.

This SOP has been extended to all web technology - Flash, AJAX calls, and so forth. Unfortunately, this means that sites that want to cooperate - say, your site and TravellerMap.com - don't have a way to share data. Flash exposes a cross-domain mechanism (where receiving second site can explicitly allow requests from requesting first site). For JavaScript there are some cross-domain-enabling proposals. But for now, JSONP is a workaround. JSONP requires that both sites cooperate – the requesting site must specify a JSONP parameter in a URL request, and the receiving site must understand the JSONP parameter and create an appropriate response.

So…. back to the question of security.

  • Does accepting JSONP requests expose TravellerMap.com to XSS attacks? No – the site does not interpret the content of the JSONP parameter beyond %-decoding. It is simply pre-pended (along with "(" prefix and ");" suffix) to the data being returned.

  • Does using JSONP requests expose your site to XSS attacks? Ah, there’s the rub. By making JSONP requests you are allowing TravellerMap.com to inject script and data into the execution context of your web pages. Let’s analyze how this could be exploited:

(1) TravellerMap.com could THEORETICALLY add malicious script to the JSONP results. In that case, instead of emitting "callback(…);" the site emits "make_forum_post(…);" or something. Since the script is executed automatically (via the JSONP mechanism) and the data can’t be filtered (it appears as a script tag which executes the content, not data you can deserialize with paranoid JSON parsing code), there is no simple defense against this. If TravellerMap.com is compromised (or I turn to the dark side…) then your site is vulnerable.

Note that this vulnerability is restricted to XSS behavior, that is, users of the site would have code executed in the context of the web site. This doesn’t allow arbitrary code execution on the victim’s machine (it’s still in the web page sandbox), or on the web server (it’s all client-side). So this isn't a "pwn'd your machine" exploit, but it is a (theoretical) "pwn'd your account" exploit.

Also note that sites like Flickr expose JSONP APIs with the same risks, so it’s not a flaw in the design of the TravellerMap.com API. Either you trust the site you’re calling into with JSONP or you don’t.

For the paranoid (i.e. you want to use the TravellerMap.com JSONP APIs, but don’t trust the site to remain exploit-free), one mitigation would be to sandbox any use of the JSONP APIs. For example, your main site might be mytravellerforum.com, and create a sandbox.mytravellerforum.com subdomain that doesn’t do anything with users' forum accounts. And of course, if you are implementing server-side functionality, you can call directly into the non-JSONP versions of the APIs, in which case you’re parsing the data yourself.

(2) TravellerMap.com could THEORETICALLY expose a vulnerability that allows JSONP requests to include arbitrary content.

Here’s how a hacker might exploit that:
  • Your site exposes a "make a map for sector X" form. The user can type the sector name into a form, which causes a JSONP request to TravellerMap.com
  • A hacker crafts a URL which makes your site make that request automatically (due to the architecture of your site, possibly a redirect). The URL includes the sector name as: "); do_evil(…); void("
  • Your page turns that into a JSONP request
  • The TravellerMap.com site THEORETICALLY blithely outputs that as part of a valid response, un-escaped
  • Your site executes that script with the do_evil() content (which, say, makes a forum post)
  • The hacker posts this nasty URL to a forum. Any user that clicks on the link ends up with do_evil() running within their authenticated context.
It’s a bit convoluted, but that’s how these XSS attacks happen. A further THEORETICAL vulnerability would be if the TravellerMap.com site allowed users to post arbitrary data which could then be retreived by a third-party site, but it does not. (No submitted data is retained by the site beyond the lifetime of the request.)

So how to mitigate it?
  • Sanitize user input on your site – don’t make arbitrary requests to external sites
  • Ensure JSONP logic exists in a sub-domain sandbox (see above)
  • Perform your own security audit – try the TravellerMap.com JSONP-enabled APIs and send test data. To the best of my knowledge, all data from successful requests are properly escaped (user data would appear as strings within JSON data) and errors are returned as 404s (the scripts should not execute).
Again, the same issues exist with any JSONP API - you must trust the site you're calling both to not return malicious scripts intentionally and to be hardened against vulnerabilities that would allow someone to cause malicious script to be returned.

So... an excellent question with a non-trivial answer. Feedback, as always, appreciated! Ω

1 comment:

Allen Varney said...

OK, I feel better now. Your answer was educational, so I'm glad I asked. Thanks!