New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Harden protection against mutation XSS caused by namespace switching #495
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
since the new approach should fix them anyway
…_FOR_JQUERY which we don't want to be back
…ever occur inside svg
LeSuisse
added a commit
to Enalean/tuleap
that referenced
this pull request
Dec 21, 2020
DOMPurify 2.2.6 comes with a new hardening to defend against mXSS. Changelog: https://github.com/cure53/DOMPurify/releases/tag/2.2.6 PR introducing the new mechanism: cure53/DOMPurify#495 Change-Id: I9ac0534d5bd200fc6db0a706ab202a029f8ae20c
This was referenced Apr 25, 2021
LeSuisse
added a commit
to Enalean/tuleap
that referenced
this pull request
Mar 30, 2022
DOMPurify 2.2.6 comes with a new hardening to defend against mXSS. Changelog: https://github.com/cure53/DOMPurify/releases/tag/2.2.6 PR introducing the new mechanism: cure53/DOMPurify#495 Change-Id: I4a499eb8efa227460040a1630ff7760f96373de8
LeSuisse
added a commit
to Enalean/tuleap
that referenced
this pull request
Feb 3, 2023
DOMPurify 2.2.6 comes with a new hardening to defend against mXSS. Changelog: https://github.com/cure53/DOMPurify/releases/tag/2.2.6 PR introducing the new mechanism: cure53/DOMPurify#495 Change-Id: Icab6ee5153969207195a67395ed5b5ec9cb134b0
This was referenced Jun 19, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This pull request is meant to add a substantial defense mechanism against instances of mutation XSS that happened within the last year as well as against (not yet disclosed) bugs exploiting differences between fragment parsing and document parsing modes.
Background & Context
One year ago I reported a DOMPurify bypass that exploited a parser bug (or actually a spec bug) in Chromium and Safari. In a nutshell, the following snippet of html:
was parsed into the following DOM tree:
(in all DOM trees, I'm using a notation that all tag names are prepended by their namespace).
When the same DOM tree is serialized, it has the following form:
When the snippet is parsed again (so we have a roundtrip: DOM tree -> markup -> DOM tree), it is parsed differently:
This is defined in HTML spec that if we're inside foreign content (that has special parsing rules, and is open by either
<svg>
or<math>
. The rules is that a specific list of tags close foreign content and go back to latest element in HTML namespace or MathML text integration point or HTML or HTML integration point (more on that later).Because DOMPurify is usually used the following way:
the round-trip of parsing into DOM tree, serializing and parsing again happens in this case.
The issue was with the following markup:
which is parsed into the following DOM tree:
DOMPurify considers the DOM tree harmless because the XSS payload is fully within
title
. Then it proceeds to serialize it to:After reparsing, a completely different DOM tree is created:
Because the foreign content was escaped when
<p>
was encountered, all subsequent tags were also parsed in HTML namespace, leading to dangerous<img>
tag in the DOM tree.Now let's have a look this year's DOMPurify bypass via namespace confusion. The payload was:
It was parsed into the following DOM tree:
The namespace is switched from
math
tohtml
on<mtext>
because it is one of MathML text integration points whose purpose is to be able to embed html elements within MathML.The DOM tree is again harmless according to DOMPurify as all elements are in the allow-list, and then serialized to:
The markup contains an embedded
form
element which is disallowed by HTML spec. Hence, when this markup is parsed again, it yields a different DOM tree:Note that
<mglyph>
was initially in HTML namespace but now it switched to MathML namespace making all subsequent tags to be parsed differently. This made it possible to close<math>
and to inject XSS payload.Even though these two examples of payloads abused different mechanics to bypass DOMPurify, I'd argue that their root cause was the same; that is, in both cases the DOM tree that DOMPurify worked on had elements in unexpected namespaces.
In the first case, there was a
<html p>
element which was a direct child of<svg svg>
or<math math>
. This can never happen in a spec-compliant parser. The only way for a child element in foreign content to have a different namespace than its parent is via well defined MathML text integration points or HTML integration points. If it happens via any other tag, then this element should be deleted.In the first case, the initial parsing yielded
<html mglyph>
. Even thoughmglyph
is in the default allow-list, it should never be in HTML namespace. Hence, it should also be dropped, rendering the whole bypass impossible.In this pull request, I'm proposing to add a code that adds a
_checkValidNamespace
function whose job is to ensure that:Here's a short overview of inner workings of the function:
svg
as it is the only way to make this switch.math
as it is the only way to make this switch.math
as it is the only way to make this switch.The second issue this pull request protect against is the differences between document parsing mode and fragment parsing mode. DOMPurify uses document parsing mode (via
new DOMParser().parseFromString()
) but it could reparse the resulting DOM tree in fragment parsing mode viainsertAdjacentHTML
. This can lead to bypasses if the result is assigned to sinks likesrcdoc
. In this pull request, I got rid ofinsertAdjacentHTML
completely and work on DOM nodes directly.Tasks
Because this pull request is a pretty significant change to DOMPurify, I'd love a double check from Mario and/or Masato and anyone else who'd like to test it :) All tests are passing so this should be fine in theory but it always worth it to check.
Comments are welcome :)