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="https://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 (though not entirely prevent!) forgeries by checking the HTTP Referer header.

Filed under:

Ready to get started?