Is getJSON() safe to call on untrusted URL?
Asked Answered
D

1

7

Is it safe to call jQuery's $.getJSON() with a URL argument that came from an untrusted source, such as another user? In other words, is it safe to call $.getJSON() with an untrusted URL? I will be careful not to trust the response and to handle the response safely, but does the call itself pose a security risk?

In other words, I'm talking about something like:

$.getJSON(url_from_user, function(...) { ... handle response safely ...});

or

$.getJSON('http://evil.com/foo.json', function(...) {...});

Could this allow code injection or XSS if some untrusted user provides a malicious value for url_from_user or if someone malicious controls the evil.com site? Again, assume that any JSON object returned will be treated safely.


More details and research I've done

The documentation for getJSON doesn't say anything either way about whether this scenario is secure. Logically, I would expect this scenario to be safe, as I would expect jQuery's implementation to download the text of the JSON object via XHR, parse this text using a safe JSON parser, and then return the JSON object.

However, after looking at the jQuery source code, I have some doubts about whether this is safe. Browsing through the source code for jQuery, it looks like this scenario might potentially allow XSS. The code for getJSON() is a bit elaborate (see src/ajax.js), but it seems to select a "transport" and then use it to send the AJAX request. I see that src/ajax/script.js registers a transport called the "script tag hack transport". This transport works roughly as follows: it adds a script tag to the document, e.g., <script src="http://evil.com/foo.json">, and registers an onload handler that runs when the downloaded script has executed. In other words, the "script tag hack transport" is fundamentally insecure if the site is controlled by an attacker: it is including attacker-controlled script into the document and executing it. Besides the script tag hack transport, there's also a XHR transport that uses the browser's XMLHttpRequest() API. I am having a hard time following the twisty logic that determines under which conditions the "script tag hack" transport will be used.

So, coming back to my original question, is it safe to call $.getJSON() with a user-provided URL? If it is potentially unsafe under some conditions, under what conditions (e.g., browser versions) is it safe/unsafe?

Dinorahdinosaur answered 13/3, 2015 at 0:26 Comment(6)
If the users themselves specify that URL, then who besides themselves could any possible XSS or whatever be hurting? Or are you planning to request those URLs for other users as well, like user A inputs the URL, and user B gets the results shown when they visit the site …?Dode
@CBroe, Yes, the latter. You can treat "user-provided" as a synonym for "came from an untrusted source". The scenario you listed would be one example of how that could occur. I've edited my question to hopefully make this clearer. Is it clear now?Dinorahdinosaur
Well, there’s two scenarios – either the data provided under the URL is valid JSON data, then it just depends on what you do with that data; or it’s not – in which case it shouldn’t be processed at all to begin with. Maybe a third one, where the URL contains some kind of code that triggers a bug in some browser that leads to a vulnerability – but that issue you could have with any kind of URL provided by a user, even something simple as an external image. Maybe request that URL server-side, check the data for validity, and only then pass it on to the client through your server …?Dode
@CBroe, did you read the next-to-last paragraph in my question? I know it's a lot of text, but I think it directly addresses your comment. You list two or three possible scenarios, but those are not the only possible scenarios. One more possibility is that jQuery itself does something that introduces XSS, regardless of what I do with the data. Indeed, I explain in detail in my question why it looks like this might be the case -- or, at least, I could not rule out that possibility. (Let's treat possible hypothetical browser bugs as out of scope, for the same reasons you elaborate.)Dinorahdinosaur
Well, the method of inserting a script element is usually used to circumvent the SOP – you can embed scripts from any source, whereas an XMLHttpRequest would only be able to load data from the same origin, or for resources that are CORS-enabled. If you can restrict the URLs that will be used to satisfy those conditions, then you can request them using XMLHttpRequest instead of having a potentially harmful script element inserted; if not, I think proxying the requests via your server where you can check the contents up-front might be the only “safe” option.Dode
@CBroe, I'm not calling XMLHttpRequest or inserting script elements. I'm calling jQuery's $.getJSON(), and under the covers jQuery's implementation of getJSON() is doing whatever it is doing (what it is doing is not under my control). More precisely, I'm reviewing code that calls $.getJSON(). So while I appreciate your brainstorming, unfortunately ideas about what I could do instead of calling $.getJSON() don't help answer my question. My question is whether $.getJSON() is safe to call with an untrusted URL. Thank you for your engagement and feedback, though.Dinorahdinosaur
C
7

Unless you configure your request to NEVER use JSONP (which jQuery will automatically try to use for some cross origin requests in some circumstances), it is not safe to use $.getJSON() against any random foreign URL.

If jQuery switches to JSONP, that would directly enable script injection into your page from the other origin since JSONP works precisely by script injection (in order to circumvent same-origin limitations with regular Ajax calls).

To prevent this type of mis-use, you will have to prevent any use of JSONP and would have to investigate the surest way to do that in jQuery. You could perhaps switch to $.ajax() where you can specify a lot more options to control things.

If this were my code, I might be tempted to even skip jQuery entirely for this one Ajax call and just use my own xmlHttpRequest object to absolutely guarantee that it was only doing a pure Ajax call (no fallback to any other transport like JSONP).

Update:

I've been trying to find a circumstance where $.getJSON() will issue a JSONP request in various test scenarios on a jsFiddle. I have not been able to find one. Either the target site has an Access-Control-Allow-Origin header that allows cross origin requests in which case jQuery just does a cross origin Ajax call or it doesn't have the header and jQuery just fails the getJSON() call. So, it looks like it would take some serious study of a specific version of jQuery to figure out if it could actually be tricked into doing a JSONP call in some sort of "auto" mode when you didn't explicitly ask for one.

Update 2: Found an actual vulnerability

I found a vulnerability. If the URL sent to $.getJSON() contains a query parameter callback=, then jQuery will execute JSONP and the target host can inject whatever script it wants with the response.

Here's a demo using a publicly accessible Flickr JSONP endpoint:

http://jsfiddle.net/jfriend00/z6ah9eh2/

This doesn't doing anything with mal-intentions, but it does execute arbitrary Javascript that is up to the target site via $.getJSON(). So, it is definitely vulnerable to code injection via JSONP.

Here's a quote from the jQuery docs for $.getJSON():

If the URL includes the string "callback=?" (or similar, as defined by the server-side API), the request is treated as JSONP instead.

And, "treated as JSONP" means it's going to insert a script tag and request and run a script from the site in the URL - thus opening a cross site scripting vulnerability if you access an untrusted site with JSONP.


One of the ideas behind JSON is that it can be parsed with a plain text parser that strictly adheres to the JSON specification and NOTHING besides pure JSON is allowed or can get through. If anyone tries to sneak some Javascript code into a JSON string, any semi-decent JSON parser will just reject the JSON as invalid and throw an exception. In a proper world (which $.getJSON() is), JSON is not parsed with the Javascript parser, it is parsed with it's own text parser that strictly accepts only valid JSON, not other Javascript constructs.

That is the idea behind a safe and secure implementation of a JSON parser which it is believed that $.getJSON() uses (there could always be unknown bugs in any parser, but work has been done to design it to be safe).

So, this hurdle has been passed. There are no tricks that can be inserted into a piece of JSON that is parsed with a decent JSON parser that will cause backdoor code injection.


Now, another hurdle depends upon what you are doing with the JSON itself and whether your handling or use of the JSON enables potential mal-behavior or not.

For example, if you pull a string property from the JSON and execute it as a method on an object without any checking to see if that string is an expected value, then your code might be able to be tricked into executing a method that you did not intend. This still doesn't insert code into your page, but it does execute something you didn't intend. You can avoid that by proper validation of the data before you use it. So, if as you say, you are using the JSON safely, then this should not be an issue.

Clorindaclorinde answered 13/3, 2015 at 0:57 Comment(3)
@Dinorahdinosaur - Added a demo of an actual JSONP vulnerability. The trick is to add callback= to the URL query parameters and then $.getJSON() will use a script tag injection to attempt a JSONP call. This allows the target site to directly inject whatever JS it wants into the response.Clorindaclorinde
Extremely helpful. Thank you for the detailed analysis and concrete demonstration of the vulnerability. I guess this is a new vulnerability vector I need to remember to avoid. Too bad the risk isn't documented in the jQuery documentation. Thanks again!Dinorahdinosaur
@Dinorahdinosaur - the jQuery doc for $.getJSON() does say this: JSONP - If the URL includes the string "callback=?" (or similar, as defined by the server-side API), the request is treated as JSONP instead. See the discussion of the jsonp data type in $.ajax() for more details. Fortunately, most people are not doing $.getJSON() on user-specified URLs (where a potentially malicious outside agent gets to specify the entire URL) so this isn't usually an issue for others. Usually the URLs being used are coded in by the developer. Your situation is different than usual.Clorindaclorinde

© 2022 - 2024 — McMap. All rights reserved.