Skip to content

Commit

Permalink
fix: Added __depth tracking for ShadowDOM and template elements as well
Browse files Browse the repository at this point in the history
fix: Set MAX_NESTING_DEPTH to 500 for good mesaure
test: Added more tests to cover template element depth tracking
  • Loading branch information
cure53 committed Apr 24, 2024
1 parent 81d963c commit 4299c0a
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 25 deletions.
19 changes: 18 additions & 1 deletion 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.

19 changes: 18 additions & 1 deletion dist/purify.es.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ function createDOMPurify() {
let CONFIG = null;

/* Specify the maximum element nesting depth to prevent mXSS */
const MAX_NESTING_DEPTH = 512;
const MAX_NESTING_DEPTH = 500;

/* Ideally, do not touch anything below this line */
/* ______________________________________________ */
Expand Down Expand Up @@ -1261,6 +1261,22 @@ function createDOMPurify() {
continue;
}

/* 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;
} else {
shadowNode.__depth = 1;
}
}

/* Remove an element if nested too deeply to avoid mXSS */
if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
shadowNode.content.__depth = shadowNode.__depth;
_forceRemove(shadowNode);
}

/* Deep shadow DOM detected */
if (shadowNode.content instanceof DocumentFragment) {
_sanitizeShadowDOM(shadowNode.content);
Expand Down Expand Up @@ -1396,6 +1412,7 @@ function createDOMPurify() {

/* Shadow DOM detected, sanitize it */
if (currentNode.content instanceof DocumentFragment) {
currentNode.content.__depth = currentNode.__depth;
_sanitizeShadowDOM(currentNode.content);
}

Expand Down
2 changes: 1 addition & 1 deletion dist/purify.es.mjs.map

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion 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.

19 changes: 18 additions & 1 deletion src/purify.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ function createDOMPurify(window = getGlobal()) {
let CONFIG = null;

/* Specify the maximum element nesting depth to prevent mXSS */
const MAX_NESTING_DEPTH = 512;
const MAX_NESTING_DEPTH = 500;

/* Ideally, do not touch anything below this line */
/* ______________________________________________ */
Expand Down Expand Up @@ -1377,6 +1377,22 @@ function createDOMPurify(window = getGlobal()) {
continue;
}

/* 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;
} else {
shadowNode.__depth = 1;
}
}

/* Remove an element if nested too deeply to avoid mXSS */
if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
shadowNode.content.__depth = shadowNode.__depth;
_forceRemove(shadowNode);
}

/* Deep shadow DOM detected */
if (shadowNode.content instanceof DocumentFragment) {
_sanitizeShadowDOM(shadowNode.content);
Expand Down Expand Up @@ -1520,6 +1536,7 @@ function createDOMPurify(window = getGlobal()) {

/* Shadow DOM detected, sanitize it */
if (currentNode.content instanceof DocumentFragment) {
currentNode.content.__depth = currentNode.__depth;
_sanitizeShadowDOM(currentNode.content);
}

Expand Down
33 changes: 17 additions & 16 deletions test/test-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -2107,41 +2107,42 @@

QUnit.test('Test proper handling of nesting-based mXSS 1/3', function (assert) {

let dirty = `${`<div>`.repeat(509)}${`</div>`.repeat(509)}<img>`;
let expected = `${`<div>`.repeat(509)}${`</div>`.repeat(509)}<img>`;
let dirty = `${`<div>`.repeat(496)}${`</div>`.repeat(496)}<img>`;
let expected = `${`<div>`.repeat(496)}${`</div>`.repeat(496)}<img>`;
let clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);
dirty = `${`<div>`.repeat(510)}${`</div>`.repeat(510)}<img>`;
expected = `${`<div>`.repeat(510)}${`</div>`.repeat(510)}<img>`;

dirty = `${`<div>`.repeat(500)}${`</div>`.repeat(500)}<img>`;
expected = `${`<div>`.repeat(498)}${`</div>`.repeat(498)}<img>`;
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);
dirty = `${`<div>`.repeat(511)}${`</div>`.repeat(511)}<img>`;
expected = `${`<div>`.repeat(510)}${`</div>`.repeat(510)}<img>`;

dirty = `${`<div>`.repeat(502)}${`</div>`.repeat(502)}<img>`;
expected = `${`<div>`.repeat(498)}${`</div>`.repeat(498)}<img>`;
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);
dirty = `${`<div>`.repeat(512)}${`</div>`.repeat(512)}<img>`;
expected = `${`<div>`.repeat(510)}${`</div>`.repeat(510)}<img>`;

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

});

QUnit.test('Test proper handling of nesting-based mXSS 2/3', function (assert) {

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

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

0 comments on commit 4299c0a

Please sign in to comment.