Version in base suite: 2.4.1+dfsg+~2.4.0-1 Base version: node-dompurify_2.4.1+dfsg+~2.4.0-1 Target version: node-dompurify_2.4.1+dfsg+~2.4.0-2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/n/node-dompurify/node-dompurify_2.4.1+dfsg+~2.4.0-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/n/node-dompurify/node-dompurify_2.4.1+dfsg+~2.4.0-2.dsc changelog | 7 + patches/CVE-2024-47875.patch | 209 +++++++++++++++++++++++++++++++++++++++++++ patches/series | 1 3 files changed, 217 insertions(+) diff -Nru node-dompurify-2.4.1+dfsg+~2.4.0/debian/changelog node-dompurify-2.4.1+dfsg+~2.4.0/debian/changelog --- node-dompurify-2.4.1+dfsg+~2.4.0/debian/changelog 2022-11-25 13:59:30.000000000 +0000 +++ node-dompurify-2.4.1+dfsg+~2.4.0/debian/changelog 2024-10-12 14:12:19.000000000 +0000 @@ -1,3 +1,10 @@ +node-dompurify (2.4.1+dfsg+~2.4.0-2) bookworm-security; urgency=medium + + * Team upload + * Fix mXSS issue (Closes: #1084983, CVE-2024-47875) + + -- Yadd Sat, 12 Oct 2024 16:12:19 +0200 + node-dompurify (2.4.1+dfsg+~2.4.0-1) unstable; urgency=medium * Team upload diff -Nru node-dompurify-2.4.1+dfsg+~2.4.0/debian/patches/CVE-2024-47875.patch node-dompurify-2.4.1+dfsg+~2.4.0/debian/patches/CVE-2024-47875.patch --- node-dompurify-2.4.1+dfsg+~2.4.0/debian/patches/CVE-2024-47875.patch 1970-01-01 00:00:00.000000000 +0000 +++ node-dompurify-2.4.1+dfsg+~2.4.0/debian/patches/CVE-2024-47875.patch 2024-10-12 14:12:19.000000000 +0000 @@ -0,0 +1,209 @@ +Description: fix for CVE-2024-47875 + Updated 2.x branch with relevant fixes for nesting-based mXSS +Author: Mario Heiderich +Origin: upstream, https://github.com/cure53/DOMPurify/commit/0ef5e537 +Bug: https://github.com/cure53/DOMPurify/security/advisories/GHSA-gx9m-whjm-85jf +Bug-Debian: https://bugs.debian.org/1084983 +Forwarded: not-needed +Reviewed-By: Yadd +Last-Update: 2024-10-12 + +--- a/src/purify.js ++++ b/src/purify.js +@@ -383,6 +383,9 @@ + /* Keep a reference to config to pass to hooks */ + let CONFIG = null; + ++ /* Specify the maximum element nesting depth to prevent mXSS */ ++ const MAX_NESTING_DEPTH = 500; ++ + /* Ideally, do not touch anything below this line */ + /* ______________________________________________ */ + +@@ -896,7 +899,13 @@ + const _isClobbered = function (elm) { + return ( + elm instanceof HTMLFormElement && +- (typeof elm.nodeName !== 'string' || ++ // 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' || + !(elm.attributes instanceof NamedNodeMap) || +@@ -1025,10 +1034,9 @@ + const childCount = childNodes.length; + + for (let i = childCount - 1; i >= 0; --i) { +- 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)); + } + } + } +@@ -1328,8 +1336,30 @@ + continue; + } + ++ /* Set the nesting depth of an element */ ++ if (shadowNode.nodeType === 1) { ++ if (shadowNode.parentNode && shadowNode.parentNode.__depth) { ++ /* ++ 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; ++ } ++ } ++ ++ /* Remove an element if nested too deeply to avoid mXSS */ ++ if (shadowNode.__depth >= MAX_NESTING_DEPTH) { ++ _forceRemove(shadowNode); ++ } ++ + /* Deep shadow DOM detected */ + if (shadowNode.content instanceof DocumentFragment) { ++ shadowNode.content.__depth = shadowNode.__depth; + _sanitizeShadowDOM(shadowNode.content); + } + +@@ -1474,8 +1504,30 @@ + continue; + } + ++ /* Set the nesting depth of an element */ ++ if (currentNode.nodeType === 1) { ++ if (currentNode.parentNode && currentNode.parentNode.__depth) { ++ /* ++ 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; ++ } ++ } ++ ++ /* Remove an element if nested too deeply to avoid mXSS */ ++ if (currentNode.__depth >= MAX_NESTING_DEPTH) { ++ _forceRemove(currentNode); ++ } ++ + /* Shadow DOM detected, sanitize it */ + if (currentNode.content instanceof DocumentFragment) { ++ currentNode.content.__depth = currentNode.__depth; + _sanitizeShadowDOM(currentNode.content); + } + +--- a/test/test-suite.js ++++ b/test/test-suite.js +@@ -2070,6 +2070,93 @@ + }); + }); + ++ QUnit.test('Test proper handling of nesting-based mXSS 1/3', function (assert) { ++ ++ let dirty = `${`
`.repeat(496)}${`
`.repeat(496)}`; ++ let expected = `${`
`.repeat(496)}${`
`.repeat(496)}`; ++ let clean = DOMPurify.sanitize(dirty); ++ assert.contains(clean, expected); ++ ++ dirty = `${`
`.repeat(500)}${`
`.repeat(500)}`; ++ expected = `${`
`.repeat(498)}${`
`.repeat(498)}`; ++ clean = DOMPurify.sanitize(dirty); ++ assert.contains(clean, expected); ++ ++ dirty = `${`
`.repeat(502)}${`
`.repeat(502)}`; ++ expected = `${`
`.repeat(498)}${`
`.repeat(498)}`; ++ clean = DOMPurify.sanitize(dirty); ++ assert.contains(clean, expected); ++ ++ dirty = `