
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.
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.
Tue, 09/28/2010 - 03:59
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.
Tue, 10/12/2010 - 21:44
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).
Mon, 11/22/2010 - 15:55
Shouldn't you also allow the dollar sign?
Mon, 11/22/2010 - 19:23
Yes, you are right that the dollar sign would be valid, though in practice I haven't seen it used in a JSONP callback.
Thu, 05/09/2013 - 12:06
To be 100% accurate, you’d have to allow much, much more than that. See Valid JavaScript variable names for details.
Wed, 04/20/2011 - 21:04
hello, thanks for sharing this code but you must send this header
header("Content-type: application/json");
anyway thank you
Fri, 06/03/2011 - 17:59
nope. jsonp is js, not json.
Thu, 04/12/2012 - 10:42
Sorry for waking up an old thread, but I stumbled on your post and I just can't understand how this is an issue at all? It may very well be my very limited understanding of XSS, so please help me out here.
The idea of XSS is that Person A can inject a script into person B:s browser right? But in this case, as no data is stored on the server, the only viable "threat" I can see here, is that a person executes code in the browser they are currently using – which should be quite similar to adding the malicious code using e.g. Firebug, right?
How can the example above ever be used to affect anyone else than Person A (who initializes the JSONP request)? I just don't understand when this could be harmful?
Thu, 04/12/2012 - 17:24
Reflected attacks are those where the injected code is reflected off the web server, such as in an error message, search result, or any other response that includes some or all of the input sent to the server as part of the request. Reflected attacks are delivered to victims via another route, such as in an e-mail message, or on some other web server. When a user is tricked into clicking on a malicious link or submitting a specially crafted form, the injected code travels to the vulnerable web server, which reflects the attack back to the user’s browser. The browser then executes the code because it came from a "trusted" server.
Wed, 07/18/2012 - 04:52
Really good intel on the reflection attack. You should put that up front in the post, in my opinion, since that's what the post is all about.
Also,
In some narrow cases you may be able to limit forgeries by checking the HTTP Referer header.
I know you said 'narrow cases', but you should really point out that the http referrer header can never be trusted.
Wed, 07/18/2012 - 17:40
Thanks for the feedback, I'm glad you found the info useful. You raise a good point about the Referer header, so I'll elaborate (and I've edited the original post to hopefully clarify the risk).
You're correct that by itself the header is not trustworthy, however when combined with a cookie and a modern browser it becomes somewhat more so. The reason is the XMLHttpRequest spec says:
4.7.2 The setRequestHeader() method…
- 5. Terminate these steps if header is a case-insensitive match for one of the following headers:
- …
- Referer
- …
It hasn't always been this way, but you have to go back quite a few years to find a browser (or vulnerable Flash plugin) that allows setRequestHeader('Referer')
. If you try it in e.g. WebKit you'll get the error "Refused to set unsafe header 'Referer'".
Putting this together, nobody should ever bet the farm on the Referer header. The "narrow cases" I alluded to are situations where the data being transmitted are so trivial (e.g. a poll) that you're willing to tolerate some small fraction of forged submissions from ancient browsers.
Wed, 07/18/2012 - 21:00
Oh neat, I didn't know that about browsers blocking referrer header spoofing. I can see how that would cut down on the reflection xss attack angle. My security experience comes mainly from the server side, where you have to worry about the entire request being forged or malicious: for example, you can't use the referrer header to ignore log-in attempts which don't come from your web portal.
Wed, 12/12/2012 - 20:45
Any discussion about how browsers behave relative to the request headers, etc, needs to include the fact that not all requests are created BY browsers or trust worthy sources, and most modern languages/scripting environments certainly allow developers to generate their own headers and spoof any sort of browser. Certainly some browsers actually will spoof other browsers for you, you just choose the browser and away you go.
Additionally, using Firebug, it's simple to make requests and interact with a page without having access to the source. You can actually modify the generated markup that the browser is executing.
NOBODY should rely on on individual headers alone, but should always depend on combinations of header values. In other cases, hardware tools such as the Big-IP will sanitize headers and protect them from tampering before passing them along to the sites they protect.
Just remember, that once you allow your application to be posted to from a remote source, you need to be EXTRA vigilant to protect it.
Fri, 09/24/2010 - 18:52