jQuery.parseHTML: Mitigate XSS vulnerability #1506

Closed
wants to merge 2 commits into
from

Projects

None yet

6 participants

@fhemberger
Contributor

Scripts passed in event attributes are executed in parseHTML immediately,
without any possibility for the user to intervene:

jQuery.parseHTML('<img src=x onerror=alert(1)>');

To mitigate this vulnerability document.implementation.createHTMLDocument()
should be used as standard context instead of document. Now the user has
to set a different context deliberately for this issue to occur.

See also GitHub issue #1505.

EDIT: Agreed to the CLA.

@fhemberger fhemberger jQuery.parseHTML: Mitigate XSS vulnerability
Scripts passed in event attributes are executed in `parseHTML` immediately,
without any possibility for the user to intervene:

`jQuery.parseHTML('<img src=x onerror=alert(1)>');`

To mitigate this vulnerability `document.implementation.createHTMLDocument()`
should be used as standard context instead of `document`. Now the user has
to set a different context deliberately for this issue to occur.

See also GitHub issue #1505.
c147522
Contributor

I haven't managed to get this also running in IE8 and below yet. Basically, it should look something like

var axContext,
    defaultContext = document;

if ( jQuery.isFunction( document.implementation.createHTMLDocument ) ) {
    defaultContext = document.implementation.createHTMLDocument();
} else if ( "ActiveXObject" in window ) {
    axContext = new ActiveXObject( "htmlfile" );
    axContext.write();
    axContext.close();
    defaultContext = axContext.body;
}
context = context || defaultContext || document;

But using the ActiveX context breaks quite a bunch of tests. But as this patch still helps for most other browsers, it's still an improvement.

@fhemberger fhemberger referenced this pull request in HubSpot/youmightnotneedjquery Jan 31, 2014
Merged

Security: Mitigate DOM XSS vulnerability #21

@fhemberger fhemberger jQuery.parseHTML: Make XSS test conditional
The current implementation relies on `document.implementation.createHTMLDocument`,
which IE8 and below are lacking. It also may not be available in older Android browsers (<= 2.x).
e684b91
Contributor

Note: I'll update this PR as soon as #1505 is merged into 2.x

Owner
dmethvin commented Feb 7, 2014

I think the problem with IE<=8 is that you end up with nodes from a foreign document that can't be added directly to the target document. If you were writing a plugin to fix this you could traverse and sanitize the elements in the foreign document, then serialize the sanitized tree to an HTML string and parse that in the target document.

Owner
dmethvin commented Feb 7, 2014

Just to give it a ticket, this is #11974 which can be reopened.

So to be clear, the most common internal use of .parseHTML() will mean that the next step will be to append the HTML to the document, triggering the inline handlers. However, we have an open ticket #14228 to provide a hook point for sanitizing input, and perhaps we could integrate a strategy into that.

Again, I suspect a complete solution is a bit heavy for including into core but would like to make it easy for someone to do the work in a plugin.

@dmethvin dmethvin added this to the 1.12/2.2 milestone Feb 7, 2014
Contributor

That's why I called the PR "mitigate" not "fix". ;) I think it's a good start to address the issue at this point. I won't put more effort in fixing this for IE<=8 – this patch improves the handling for ~90% of the users, for the rest it's simply the same as before.

A plug-in seems like a good idea – would parseHTML be the only function to hook into? It would basically use the changes proposed in #1508, but as the user has to install this separately, it won't have unwanted side effects in core.

Member
markelog commented Dec 3, 2014

Since this is the one of the last 1.x-master PR's and it seems solution wouldn't cover all browsers but fix for #1747 will, so i would like to close it and #1505 too.

Owner
dmethvin commented Dec 3, 2014

Per the comment above I think this is actually part of the solution for gh-1747 because it prevents some inline code from running before the HTML is appended to the document. The two PRs could be merged though.

Member
markelog commented Dec 3, 2014

@dmethvin misinterpreted your comment, now i see the conundrum, so we have two problems: createHTMLDocument doesn't exist in IE8 and in old android otherwise we good.

So createHTMLDocument does exist in android 2.3 and in IE8 we could use an active X object.

Sounds good?

Contributor

#1505 is for jQuery 2.x, the proposed solution works fine there.

Regarding this PR, IE8 could use an ActiveX component for filtering, but it seems to break some stuff in jQuery I wasn't able to figure out.

A quick (although not complete) solution would be to use document.implementation.createHTMLDocument where it's available, otherwise falling back to the current behavior. Although this would leave IE8 and below still vulnerable to those kind of injections, the issue would be fixed for all other browsers.

The Active X component would require some additional work and could be added to a later patch release.

Owner
dmethvin commented Dec 3, 2014

I think the quick incomplete solution would be fine. That leaves the problem unsolved for IE8, but many problems are unsolved for IE8. 😈

Member
markelog commented Dec 8, 2014

Fixed execution with activex component, but it was throwing in indeed unexpected places, given that parseHTML is one is the basis for many code paths i think risks are pretty high, so let's IE8 problem be unresolved.

Will try to take time for this and #1505 to land tomorrow

@gibson042 gibson042 commented on the diff Dec 8, 2014
src/core/parseHTML.js
var parsed = rsingleTag.exec( data ),
- scripts = !keepScripts && [];
+ scripts = !keepScripts && [],
+ // document.implementation stops scripts or inline event handlers from being executed immediately
+ /* jshint laxbreak: true */
+ defaultContext = jQuery.isFunction( document.implementation.createHTMLDocument )
gibson042
gibson042 Dec 8, 2014 Member

Instead of being run inline, this check should be executed once with the results captured as a support property.

gibson042
gibson042 Dec 8, 2014 Member

Also, please undo the jshint exception and move the ternary ? and : to the end of the preceding lines (see http://contribute.jquery.org/style-guide/js/#multi-line-statements ).

@gibson042 gibson042 commented on the diff Dec 8, 2014
test/unit/core.js
@@ -1339,6 +1339,23 @@ test("jQuery.parseHTML", function() {
equal( jQuery.parseHTML("<td><td>")[ 1 ].parentNode.nodeType, 11, "parentNode should be documentFragment" );
});
+// This XSS test is optional, as it will only pass when `document.implementation.createHTMLDocument`
+// is implemented. It is not available in IE8 and below and might not be for older Android
+// browsers (<= 2.x) either.
+if ( jQuery.isFunction( document.implementation.createHTMLDocument ) ) {
gibson042
gibson042 Dec 8, 2014 Member

We shouldn't be using the jQuery under test to make decisions about what to test. Either switch to supportjQuery or just drop the isFunction and check for existence of document.implementation.createHTMLDocument.

Also, this would make a great use of the newly-available QUnit.skip.

dmethvin
dmethvin Dec 8, 2014 Owner

Also, this would make a great use of the newly-available QUnit.skip.

Ooh yeah, i forgot that was being added!

Member
markelog commented Dec 8, 2014

@fhemberger when @gibson042 comments would be taken into the account, we're happy to merge it :-).

btw, thank you for all your patience with this!

@timmywil timmywil pushed a commit that referenced this pull request Dec 10, 2014
Timmy Willison Core: introduce createHTMLDocument in parseHTML; Safari 8/IE8 left out
Close gh-1506
828a718
Owner

Closed by 828a718

@timmywil timmywil closed this Dec 10, 2014
Contributor

Wow, great. Thank you! 👍

Member
mgol commented Mar 6, 2016

This PR was reverted on 1.12-stable so I'm removing the milestone. We'll still try to fix the issues that popped up in 3.0.0 and not revert this patch there so I'm keeping the milestone on #1505.

(the issue to resolve is #2941).

@mgol mgol removed this from the 3.0.0 milestone Mar 6, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment