Skip to content

Commit

Permalink
Merge pull request #941 from icesfont/fix/deep-nesting-mxss
Browse files Browse the repository at this point in the history
fix: added __removalCount to account for nodes removed from parents w…
  • Loading branch information
cure53 committed Apr 25, 2024
2 parents 6dbc2bd + 813d065 commit 1f494b9
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 35 deletions.
23 changes: 16 additions & 7 deletions dist/purify.cjs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/purify.cjs.js.map

Large diffs are not rendered by default.

23 changes: 16 additions & 7 deletions dist/purify.es.mjs
Expand Up @@ -930,7 +930,9 @@ function createDOMPurify() {
const _isClobbered = function _isClobbered(elm) {
return elm instanceof HTMLFormElement && (
// eslint-disable-next-line unicorn/no-typeof-undefined
typeof elm.__depth !== 'undefined' && typeof elm.__depth !== 'number' || typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
typeof elm.__depth !== 'undefined' && typeof elm.__depth !== 'number' ||
// eslint-disable-next-line unicorn/no-typeof-undefined
typeof elm.__removalCount !== 'undefined' && typeof elm.__removalCount !== 'number' || typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
};

/**
Expand Down Expand Up @@ -1028,8 +1030,9 @@ function createDOMPurify() {
if (childNodes && parentNode) {
const childCount = childNodes.length;
for (let i = childCount - 1; i >= 0; --i) {
childNodes[i].__depth++;
parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
const childClone = cloneNode(childNodes[i], true);
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
parentNode.insertBefore(childClone, getNextSibling(currentNode));
}
}
}
Expand Down Expand Up @@ -1264,9 +1267,12 @@ function createDOMPurify() {

/* Set the nesting depth of an element */
if (shadowNode.nodeType === 1) {
// eslint-disable-next-line unicorn/prefer-ternary
if (shadowNode.parentNode && shadowNode.parentNode.__depth) {
shadowNode.__depth = shadowNode.parentNode.__depth + 1;
/*
We want the depth of the node in the original tree, which can
change when it's removed from its parent.
*/
shadowNode.__depth = (shadowNode.__removalCount || 0) + shadowNode.parentNode.__depth + 1;
} else {
shadowNode.__depth = 1;
}
Expand Down Expand Up @@ -1398,9 +1404,12 @@ function createDOMPurify() {

/* Set the nesting depth of an element */
if (currentNode.nodeType === 1) {
// eslint-disable-next-line unicorn/prefer-ternary
if (currentNode.parentNode && currentNode.parentNode.__depth) {
currentNode.__depth = currentNode.parentNode.__depth + 1;
/*
We want the depth of the node in the original tree, which can
change when it's removed from its parent.
*/
currentNode.__depth = (currentNode.__removalCount || 0) + currentNode.parentNode.__depth + 1;
} else {
currentNode.__depth = 1;
}
Expand Down
2 changes: 1 addition & 1 deletion dist/purify.es.mjs.map

Large diffs are not rendered by default.

23 changes: 16 additions & 7 deletions dist/purify.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/purify.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/purify.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/purify.min.js.map

Large diffs are not rendered by default.

31 changes: 22 additions & 9 deletions src/purify.js
Expand Up @@ -940,6 +940,9 @@ function createDOMPurify(window = getGlobal()) {
// eslint-disable-next-line unicorn/no-typeof-undefined
((typeof elm.__depth !== 'undefined' &&
typeof elm.__depth !== 'number') ||
// eslint-disable-next-line unicorn/no-typeof-undefined
(typeof elm.__removalCount !== 'undefined' &&
typeof elm.__removalCount !== 'number') ||
typeof elm.nodeName !== 'string' ||
typeof elm.textContent !== 'string' ||
typeof elm.removeChild !== 'function' ||
Expand Down Expand Up @@ -1066,11 +1069,9 @@ function createDOMPurify(window = getGlobal()) {
const childCount = childNodes.length;

for (let i = childCount - 1; i >= 0; --i) {
childNodes[i].__depth++;
parentNode.insertBefore(
cloneNode(childNodes[i], true),
getNextSibling(currentNode)
);
const childClone = cloneNode(childNodes[i], true);
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
parentNode.insertBefore(childClone, getNextSibling(currentNode));
}
}
}
Expand Down Expand Up @@ -1380,9 +1381,15 @@ function createDOMPurify(window = getGlobal()) {

/* Set the nesting depth of an element */
if (shadowNode.nodeType === 1) {
// eslint-disable-next-line unicorn/prefer-ternary
if (shadowNode.parentNode && shadowNode.parentNode.__depth) {
shadowNode.__depth = shadowNode.parentNode.__depth + 1;
/*
We want the depth of the node in the original tree, which can
change when it's removed from its parent.
*/
shadowNode.__depth =
(shadowNode.__removalCount || 0) +
shadowNode.parentNode.__depth +
1;
} else {
shadowNode.__depth = 1;
}
Expand Down Expand Up @@ -1522,9 +1529,15 @@ function createDOMPurify(window = getGlobal()) {

/* Set the nesting depth of an element */
if (currentNode.nodeType === 1) {
// eslint-disable-next-line unicorn/prefer-ternary
if (currentNode.parentNode && currentNode.parentNode.__depth) {
currentNode.__depth = currentNode.parentNode.__depth + 1;
/*
We want the depth of the node in the original tree, which can
change when it's removed from its parent.
*/
currentNode.__depth =
(currentNode.__removalCount || 0) +
currentNode.parentNode.__depth +
1;
} else {
currentNode.__depth = 1;
}
Expand Down
36 changes: 36 additions & 0 deletions test/test-suite.js
Expand Up @@ -2126,6 +2126,13 @@
expected = `<template>${`<div>`.repeat(498)}${`</div>`.repeat(498)}<img>`;
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);

dirty = `<div><template>${`<r>`.repeat(497)}<img>${`</r>`.repeat(
497
)}</template></div><img>`;
expected = `<div><template></template></div><img>`;
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);

});

Expand All @@ -2146,6 +2153,30 @@
];
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);

dirty = `<form><input name="__removalCount">${`<div>`.repeat(
500
)}${`</div>`.repeat(500)}<img>`;
expected = [
``,
`<form><input name="__removalCount">${`<div>`.repeat(
497
)}${`</div>`.repeat(497)}<img></form>`,
];
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);

dirty = `<form><input name="__removalCount"></form>${`<div>`.repeat(
500
)}${`</div>`.repeat(500)}<img>`;
expected = [
`${`<div>`.repeat(498)}${`</div>`.repeat(498)}<img>`,
`<form><input name="__removalCount"></form>${`<div>`.repeat(
498
)}${`</div>`.repeat(498)}<img>`,
];
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);
});

QUnit.test('Test proper handling of nesting-based mXSS 3/3', function (assert) {
Expand All @@ -2154,6 +2185,11 @@
let expected = [``, `<form><input name="__depth"></form>`];
let clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);

dirty = `<form><input name="__removalCount">`;
expected = [``, `<form><input name="__removalCount"></form>`];
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);
});
};
});

0 comments on commit 1f494b9

Please sign in to comment.