Skip to content

Commit

Permalink
fix: Hardened the depth tracking code against prototype pollution
Browse files Browse the repository at this point in the history
  • Loading branch information
cure53 committed May 8, 2024
1 parent 8df72f1 commit 1e52026
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 117 deletions.
15 changes: 11 additions & 4 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.

15 changes: 11 additions & 4 deletions dist/purify.es.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const stringTrim = unapply(String.prototype.trim);
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
const regExpTest = unapply(RegExp.prototype.test);
const typeErrorCreate = unconstruct(TypeError);
const numberIsNaN = unapply(Number.isNaN);

/**
* Creates a new function that calls the given function with a specified thisArg and arguments.
Expand Down Expand Up @@ -1307,8 +1308,11 @@ function createDOMPurify() {
}
}

/* Remove an element if nested too deeply to avoid mXSS */
if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
/*
* Remove an element if nested too deeply to avoid mXSS
* or if the __depth might have been tampered with
*/
if (shadowNode.__depth >= MAX_NESTING_DEPTH || numberIsNaN(shadowNode.__depth)) {
_forceRemove(shadowNode);
}

Expand Down Expand Up @@ -1445,8 +1449,11 @@ function createDOMPurify() {
}
}

/* Remove an element if nested too deeply to avoid mXSS */
if (currentNode.__depth >= MAX_NESTING_DEPTH) {
/*
* Remove an element if nested too deeply to avoid mXSS
* or if the __depth might have been tampered with
*/
if (currentNode.__depth >= MAX_NESTING_DEPTH || numberIsNaN(currentNode.__depth)) {
_forceRemove(currentNode);
}

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

Large diffs are not rendered by default.

15 changes: 11 additions & 4 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.

21 changes: 17 additions & 4 deletions src/purify.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
stringToString,
stringIndexOf,
stringTrim,
numberIsNaN,
regExpTest,
typeErrorCreate,
lookupGetter,
Expand Down Expand Up @@ -1426,8 +1427,14 @@ function createDOMPurify(window = getGlobal()) {
}
}

/* Remove an element if nested too deeply to avoid mXSS */
if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
/*
* Remove an element if nested too deeply to avoid mXSS
* or if the __depth might have been tampered with
*/
if (
shadowNode.__depth >= MAX_NESTING_DEPTH ||
numberIsNaN(shadowNode.__depth)
) {
_forceRemove(shadowNode);
}

Expand Down Expand Up @@ -1577,8 +1584,14 @@ function createDOMPurify(window = getGlobal()) {
}
}

/* Remove an element if nested too deeply to avoid mXSS */
if (currentNode.__depth >= MAX_NESTING_DEPTH) {
/*
* Remove an element if nested too deeply to avoid mXSS
* or if the __depth might have been tampered with
*/
if (
currentNode.__depth >= MAX_NESTING_DEPTH ||
numberIsNaN(currentNode.__depth)
) {
_forceRemove(currentNode);
}

Expand Down
4 changes: 4 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ const regExpTest = unapply(RegExp.prototype.test);

const typeErrorCreate = unconstruct(TypeError);

const numberIsNaN = unapply(Number.isNaN);

/**
* Creates a new function that calls the given function with a specified thisArg and arguments.
*
Expand Down Expand Up @@ -215,6 +217,8 @@ export {
stringToLowerCase,
stringToString,
stringTrim,
// Number
numberIsNaN,
// Errors
typeErrorCreate,
// Other
Expand Down
96 changes: 0 additions & 96 deletions test/test-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -2104,101 +2104,5 @@
let clean = DOMPurify.sanitize(dirty, config);
assert.contains(clean, expected);
});

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

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

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

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

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

dirty = `<div><template>${`<r>`.repeat(255)}<img>${`</r>`.repeat(
255
)}</template></div><img>`;
expected = `<div><template></template></div><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(500)}${`</div>`.repeat(500)}<img>`;
let expected = [
``,
`<form><input>${`<div>`.repeat(252)}${`</div>`.repeat(252)}<img></form>`,
];
let clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);

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

dirty = `<form><input name="__removalCount">${`<div>`.repeat(
500
)}${`</div>`.repeat(500)}<img>`;
expected = [
``,
`<form><input>${`<div>`.repeat(
252
)}${`</div>`.repeat(252)}<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(253)}${`</div>`.repeat(253)}<img>`,
`<form><input></form>${`<div>`.repeat(
253
)}${`</div>`.repeat(253)}<img>`,
];
clean = DOMPurify.sanitize(dirty);
assert.contains(clean, expected);

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

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

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

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

0 comments on commit 1e52026

Please sign in to comment.