Hi, here is a debdiff for bookworm
Best regards, Xavier On 10/12/24 11:36, Moritz Mühlenhoff wrote:
Source: node-dompurify X-Debbugs-CC: t...@security.debian.org Severity: grave Tags: security Hi, The following vulnerability was published for node-dompurify. CVE-2024-47875[0]: | DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for | HTML, MathML and SVG. DOMpurify was vulnerable to nesting-based | mXSS. This vulnerability is fixed in 2.5.0 and 3.1.3. https://github.com/cure53/DOMPurify/security/advisories/GHSA-gx9m-whjm-85jf https://github.com/cure53/DOMPurify/commit/0ef5e537a514f904b6aa1d7ad9e749e365d7185f https://github.com/cure53/DOMPurify/commit/6ea80cd8b47640c20f2f230c7920b1f4ce4fdf7a If you fix the vulnerability please also make sure to include the CVE (Common Vulnerabilities & Exposures) id in your changelog entry. For further information see: [0] https://security-tracker.debian.org/tracker/CVE-2024-47875 https://www.cve.org/CVERecord?id=CVE-2024-47875 Please adjust the affected versions in the BTS as needed.
diff --git a/debian/changelog b/debian/changelog index e109eb4..02c7a01 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 <y...@debian.org> Sat, 12 Oct 2024 16:12:19 +0200 + node-dompurify (2.4.1+dfsg+~2.4.0-1) unstable; urgency=medium * Team upload diff --git a/debian/patches/CVE-2024-47875.patch b/debian/patches/CVE-2024-47875.patch new file mode 100644 index 0000000..9ebc9e4 --- /dev/null +++ b/debian/patches/CVE-2024-47875.patch @@ -0,0 +1,209 @@ +Description: fix for CVE-2024-47875 + Updated 2.x branch with relevant fixes for nesting-based mXSS +Author: Mario Heiderich <ma...@cure53.de> +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 <y...@debian.org> +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 = `${`<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(500)}${`</div>`.repeat(500)}<img>`; ++ expected = `${`<div>`.repeat(498)}${`</div>`.repeat(498)}<img>`; ++ clean = DOMPurify.sanitize(dirty); ++ assert.contains(clean, expected); ++ ++ 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 = `<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); ++ ++ 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); ++ ++ }); ++ ++ 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 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(500)}${`</div>`.repeat(500)}<img>`; ++ expected = [ ++ `${`<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); ++ ++ 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) { ++ ++ let dirty = `<form><input name="__depth">`; ++ 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); ++ }); ++ + QUnit.test('removeHook returns hook function', function (assert) { + const entryPoint = 'afterSanitizeAttributes'; + const dirty = '<div class="hello"></div>'; diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..d5a92ec --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +CVE-2024-47875.patch
-- Pkg-javascript-devel mailing list Pkg-javascript-devel@alioth-lists.debian.net https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/pkg-javascript-devel