Cabecera Penligente

Asegure su Frontend: Hoja de trucos actualizada sobre XSS basado en DOM

Introducción

Una hoja de trucos de XSS basado en DOM es su referencia obligada cuando desee localizar, prevenir y automatizar la protección contra la inyección de scripts en el lado del cliente. En esencia: identificar dónde la entrada controlada por el usuario (un fuente) desemboca en una API peligrosa (a fregadero), sustitúyalo por patrones seguros (utilice textoContenido, createElemento sanitizers), e integre comprobaciones en su flujo de trabajo de compilación, tiempo de ejecución y pentest. Dado que el DOM XSS se produce por completo en el navegador y elude muchos de los filtros tradicionales del lado del servidor, el front-end se convierte en la última línea de defensa, y en el lugar donde la mayoría de los equipos aún carecen de automatización.

Qué es el XSS basado en DOM y por qué es importante

Según PortSwigger, el cross-site scripting basado en DOM surge cuando "JavaScript toma datos de una fuente controlable por el atacante, como la URL, y los pasa a un sumidero que admite la ejecución dinámica de código, como por ejemplo eval() o innerHTML." portswigger.net La hoja de trucos de prevención de XSS basado en DOM de OWASP subraya que la diferencia clave con el XSS almacenado/reflejado es la siguiente inyección del lado del cliente en tiempo de ejecución. cheatsheetseries.owasp.org+1

En las aplicaciones modernas - Single Page Apps (SPAs), uso intensivo de widgets de terceros, construcción DOM dinámica - el riesgo aumenta: las cargas útiles pueden no llegar nunca a los registros del servidor, los WAF tradicionales pueden pasarlas por alto, y los desarrolladores a menudo no tienen suficientemente en cuenta los siguientes factores identificadores de fragmentos, flujos postMessageo nombre.ventanatodas ellas fuentes comunes. Reconocer este cambio es el primer paso para medir la madurez de su seguridad.

Cartografía de la amenaza: fuentes → sumideros

La codificación segura comienza con un mapa mental de fuentes (por donde entra la entrada del atacante) y fregaderos (donde se produce la ejecución). Un blog de seguridad lo resume: "La fuente es cualquier lugar de una página web donde se pueden añadir entradas de usuario... El sumidero es donde van los datos insertados en la fuente... y si no se desinfectan pueden dar lugar a una vulnerabilidad XSS basada en DOM". Medio

A continuación encontrará una tabla compacta que debería tener en su puesto de trabajo y en las listas de control de revisión:

Fuente (punto de entrada)Fregadero (API peligrosa)
location.hash, location.search, URLSearchParamsinnerHTML, insertAdjacentHTML, outerHTML
postMensaje, nombre.ventanaeval(), new Function(), setTimeout(string)
document.referrer, localStorage, sessionStoragesetAttribute('on...'), element.src = ...
Datos de widgets de terceros no verificadosCualquier inserción DOM o ejecución implícita de código

Como subraya OWASP, ninguna técnica por sí sola evita el XSS: hay que combinar sumideros adecuados, codificación, desinfección y API seguras. cheatsheetseries.owasp.org

Patrones de ataque del mundo real con código y soluciones

Ejemplo A - Inyección de selector jQuery mediante fragmento de URL

Fragmento vulnerable:

js

$(window).on('hashchange', () => {

const target = $(location.hash); // controlado por el usuario

target[0].scrollIntoView();

});

Un atacante puede crear https://site/page.html#<img src="x" onerror="alert(1)"> - jQuery trata el hash como HTML/selector y el botón onerror disparadores.

Arréglalo:

js

const raw = localización.hash.slice(1);

const safeId = /^[A-Za-z0-9_-]+$/.test(raw) ? raw : null;

if (safeId) {

const target = document.getElementById(safeId);

if (target) target.scrollIntoView();

}

Clave: validar, tratar hash como identificador no HTML.

Ejemplo B - postMessage → cadena eval

Fragmento vulnerable:

js

window.addEventListener('message', e => {

eval(e.data); // peligroso

});

Versión corregida:

window.addEventListener('mensaje', evento => {

if (event.origin !== '') return;

intentar {

const msg = JSON.parse(evento.datos);

handleMessage(msg);

} catch {

console.warn('Formato de mensaje no válido');

}

});

Evite evalúecomprobar el origen, utilizar un análisis sintáctico seguro.

Ejemplo C - Editor/Preview XSS en contexto markdown

Vulnerable:

js

preview.innerHTML = marcado(userInput);

Seguro:

js

import DOMPurify from 'dompurify';

const dirty = marcado(userInput);

const clean = DOMPurify.sanitize(dirty);

preview.innerHTML = clean;

Cuando se permite HTML generado por el usuario, se requiere sanitización.

Implantaciones desplegables: hacerlo operativo

Ayudante DOM seguro - safe-dom.js

js

import DOMPurify from 'dompurify';

export function setSafeHTML(el, dirty) {

const clean = DOMPurify.sanitize(dirty, {

ALLOWED_TAGS: ['b','i','a','p','ul','li','code','pre','img'],

ALLOWED_ATTR: ['href','src','alt','title','rel'],

FORBID_ATTR: ['onerror','onclick','style']

});

el.innerHTML = limpio;

}

export function setText(el, text) {

el.textContent = String(text ?? '');

}

export function safeSetAttribute(el, name, val) {

if (/^on/i.test(name)) throw new Error('Event handler attribute not allowed');

el.setAttribute(name, String(val));

}

Utilice esta biblioteca para centralizar las operaciones DOM seguras y reducir los errores humanos.

Aplicación estática - Muestra ESLint

js

// .eslintrc.js

reglas: {

'no-restricted-syntax': [

'error',

{ selector: "AsignaciónExpresión[left.property.name='innerHTML']", mensaje: "Utilice safe-dom.setSafeHTML o textContent". },

{ selector: "CallExpression[callee.name='eval']", message: "Evita eval()" },

{ selector: "CallExpression[callee.property.name='write']", mensaje: "Evita document.write()" }

]

}

Combinar con ganchos pre-commit (husky, lint‑staged) para bloquear patrones peligrosos.

CI / Puppeteer test - Acciones GitHub

.github/workflows/dom-xss.yml activa una prueba de Puppeteer:

// tests/puppeteer/dom-xss-test.js

js

const puppeteer = require('puppeteer');

(async ()=>{

const browser = await puppeteer.launch();

const page = await browser.newPage();

const logs = [];

page.on('console', msg => logs.push(msg.text()));

await page.goto(${URL}/page.html#<img src="x" onerror="console.log("xss")">);

await page.waitForTimeout(1000);

if (logs.some(l=>l.includes('XSS'))) process.exit(2);

await browser.close();

})();

Fracaso construir en la detección.

Supervisión en tiempo de ejecución - MutationObserver

js

(function(){

const obs = new MutationObserver(muts=>{

muts.forEach(m=>{

m.addedNodes.forEach(n=>{

if(n.nodeType===1){

const html = n.innerHTML || '';

if(/on(error|click|load)|<script\b/i.test(html)){

navigator.sendBeacon('/_monitoring/xss', JSON.stringify({

url: location.href, fragmento: html.slice(0,200)

}));

}

}

});

});

});

obs.observe(document.documentElement, {childList:true, subtree:true});

})();

Útil en la puesta en escena para alertar de inyecciones DOM inesperadas.

Refuerzo de la seguridad de los navegadores - CSP y tipos de confianza

Cabecera CSP:

pgsql

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-XYZ'; object-src 'none'; base-uri 'self';

Fragmento de tipos de confianza:

js

window.trustedTypes?.createPolicy('safePolicy', {

createHTML: s => { throw new Error('Asignación directa de HTML bloqueada'); },

createScript: s => { throw new Error('Creación directa de script bloqueada'); }

});

Esto deniega por defecto los sumideros no fiables.

Seguridad de los guiones de terceros - SRI

bash

openssl dgst -sha384 -binary vendor.js | openssl base64 -A

Utilice <script src="vendor.js" integrity="sha384‑..." crossorigin="anonymous"></script> para fijar y verificar.

Integración con Penligent para la automatización

Si su equipo utiliza Penligent, puede elevar su protección DOM XSS a una canalización continua. El artículo de investigación de Penligent señala cómo "Detección de XSS basado en DOM a través del rastreo de taint en tiempo de ejecución ... las técnicas del lado del servidor no pueden detectar de forma fiable la inyección del lado del cliente". Penligente

Ejemplo de flujo de trabajo:

  1. En CI, active una exploración Penligent con el conjunto de reglas dom-xssproporcionando cargas útiles como #<img src="x" onerror="alert(1)">.
  2. Penligent ejecuta flujos headless, genera PoC y devuelve los resultados a través de webhook.
  3. CI analiza los hallazgos: si la gravedad es ≥ alta, falla la compilación y anota el PR con la recomendación de carga útil + sumidero + corrección (por ejemplo, "reemplazar..."). innerHTML con safe-dom.setSafeHTML).
  4. Los desarrolladores arreglan, ejecutan CI de nuevo, fusionan sólo cuando está verde.

Así se cierra el bucle: de referencia (esta chuleta) → política de código → detección automatizada → remediación organizada.

Conclusión

El front-end ya no es "sólo interfaz de usuario". Es una superficie de ataque. Esta hoja de trucos le guía a través de la comprensión de la inyección del lado del cliente, la sustitución de sumideros peligrosos, la construcción de bibliotecas de ayuda seguras, el despliegue de detección estática/CI/runtime, y la automatización con una plataforma como Penligent. Sus próximos pasos: escanear su código base en busca de sumideros prohibidos (innerHTML, evalúe), adopte una safe-dom hacer cumplir las reglas de lint, integrar pruebas headless y lógica pentest real, monitorizar producción/staging, y fijar recursos de terceros. La protección de DOM XSS consiste en imposible para pasar desapercibidos, sin confiar únicamente en el azar.

Comparte el post:
Entradas relacionadas
es_ESSpanish