[Pkg-javascript-devel] Bug#1084983: Bug#1084983: node-dompurify: CVE-2024-47875

Yadd yadd at debian.org
Sat Oct 12 15:14:14 BST 2024


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: team at 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.
> 
-------------- next part --------------
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 <yadd at 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 <mario at 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 <yadd at 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


More information about the Pkg-javascript-devel mailing list