Using JSONP Safely

JSONP is a way to make cross-domain requests through javascript. It takes advantage of a loophole in the browser's same-origin policy: <script src="http://anywhere.com/data.jsonp"></script> tags are allowed to load files from any domain. This differs from normal AJAX requests, which are only allowed to make requests to the same domain as the page you are viewing.

Unfortunately, many guides to implementing this technique in PHP contain a dangerous security flaw. Can you spot it?

Here is the problem code:

echo $_GET['callback'] . '(' . $jsonData . ');';

This is a textbook cross-site scripting (XSS) vulnerability. Because the value of $_GET['callback'] isn't sanitized, any arbitrary code can be injected via the URL. Combined with an incorrect Content-Type header, the response will be interpreted as text/html and executed by your browser.

The right way

Here's how to avoid this flaw:

function generate_jsonp($data) {
  if (preg_match('/\W/', $_GET['callback'])) {
    // if $_GET['callback'] contains a non-word character,
    // this could be an XSS attack.
    header('HTTP/1.1 400 Bad Request');
    exit();
  }
  header('Content-type: application/javascript; charset=utf-8');
  print sprintf('%s(%s);', $_GET['callback'], json_encode($data));
}

If you're using Drupal, and the callback value is expected to be unique (as it is with jQuery.ajax()), you may also want to set $GLOBALS['conf']['cache'] = CACHE_DISABLED. This will prevent the page cache from filling up with one-time responses.

Other security considerations

JSONP has some other limitations, too: It can only be used for GET requests, and there's no general way to prevent cross-site request forgeries*. It's bad for private data, since any site on the web could hijack a JSONP response if the URL is known. This means it's best suited for consumption of public data feeds.

Soon (translation: as soon as IE catches up to the rest of the web) we'll have broad support for Cross-Origin Resource Sharing, and we can let JSONP die quietly.

*You might at first think unique XSRF tokens or doubly-submitted cookies would still work, but the token would have to be retrieved via another JSONP request which could itself be forged. In some narrow cases you may be able to limit forgeries by checking the HTTP Referer header.

Comments

Really interested in exploring how you could leverage this with OpenLayers to call dynamic KML files on remote hosts. Right now you have to setup a proxy, but something like this would be much easier on my end-users managing mapping sites.

dylan's picture

Yes this would definitely work, but it would need the data provider to cooperate by providing JSONP formatted data.

You can reformat it yourself but then you're back to needing a proxy... OTOH the proxy could be anywhere - it need not run under your domain. Perhaps a "universal KML / JSONP translation service" would be valuable. If you restrict it to just KML files the risk of abuse seems fairly minimal.

I too can't wait for CORS to become mainstream and for JSONP to die :)

Actually, IE8 has partial support for CORS (partial in the sense that they decided to limit the CORS features available to JavaScript apps to reduce some of the security risks of leaking confidential user data to third party sites).

dylan's picture

Yes, you are right that the dollar sign would be valid, though in practice I haven't seen it used in a JSONP callback.

Add new comment