Respuesta rápida
Una plataforma de IA conversacional permitía adjuntar SVGs en el chat. Un parche previo bloqueaba javascript: en href de SVG, pero una variante del payload conseguía bypassarlo. Como el SVG se renderizaba inline en el DOM principal, cualquier participante del chat que hiciera click veía ejecutarse JavaScript en el contexto de la app, no solo el atacante. Reclasificación de Self-XSS → Stored XSS de impacto multi-usuario.
Contexto: clasificación original y por qué se reclasificó
El reporte se clasificó inicialmente como duplicado de un Self-XSS previo (variante que solo afecta al propio atacante y por tanto se considera de bajo impacto).
La reclasificación se basa en dos diferencias fundamentales:
- Bypassa el filtro que neutralizaba el payload del reporte anterior.
- El SVG se renderiza en el chat compartido, así que cualquier otro participante de la conversación queda expuesto al payload sin más acción que abrir el chat.
Eso lo convierte en Stored XSS con alcance a terceros, no en Self-XSS.
El payload
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<a href="javascript:alert(document.domain)">
<text x="50" y="50" text-anchor="middle">click me</text>
</a>
</svg>
El SVG se sube como adjunto en cualquier chat. La plataforma lo renderiza inline en la interfaz. El elemento <text> aparece como texto normal en pantalla. Al hacer click, se ejecuta alert(document.domain) en el contexto del dominio principal.
El payload usa el esquema javascript: en el atributo href de un elemento <a> dentro del SVG — el mismo vector que el reporte previo, pero con una codificación o estructura que supera el filtro que bloqueaba el original.
Por qué no es Self-XSS
La distinción clave entre Self-XSS y Stored XSS en este caso es el scope del almacenamiento:
| Self-XSS (variante anterior) | Este reporte | |
|---|---|---|
| ¿Dónde se ejecuta? | Solo en la sesión del atacante | En el chat compartido |
| ¿Quién queda expuesto? | Solo el propio atacante | Cualquier participante del chat |
| ¿Persiste para otros? | No | Sí — el SVG está almacenado en la conversación |
| ¿Afecta a agentes de soporte? | No | Sí — si abren el chat |
El payload está almacenado en el servidor dentro de la conversación. Cualquier usuario que tenga acceso a ese chat — invitado, participante o agente de soporte — ve el SVG renderizado y queda expuesto al payload.
CSP y renderizado inline
El reporte indica que el payload no se ejecuta desde el CDN y que bypassa el CSP. Esto apunta a que la plataforma renderiza el SVG directamente en el DOM del documento principal en lugar de cargarlo como recurso externo desde un CDN.
Cuando el SVG se renderiza inline en el DOM del documento principal, hereda el contexto de ejecución de la app. El javascript: en un href de un SVG inline no está bloqueado por la CSP si el evento de click lo desencadena el usuario, ya que no es un script externo sino una navegación a un URI javascript:.
Flujo de ataque
[Atacante] sube SVG con <a href="javascript:..."> en un chat
↓
Servidor almacena el SVG en la conversación, filtro no lo bloquea
↓
SVG renderizado inline en el chat UI
↓
[Atacante] comparte el link del chat (o un agente de soporte lo abre)
↓
[Víctima] abre el chat → SVG visible con texto clickable
↓
[Víctima] hace click en el texto
↓
javascript: ejecutado en el contexto del dominio principal
Impacto observado
- El SVG se almacena en el chat y persiste para todos los participantes.
- La ejecución de JavaScript ocurre en el contexto del dominio principal, con acceso a
document.domain. - El payload bypassa tanto el filtro de sanitización del SVG como la CSP vigente.
- Los agentes de soporte que abran la conversación quedan igualmente expuestos.
Clasificación técnica
| Campo | Valor |
|---|---|
| Tipo de vulnerabilidad | Stored XSS — SVG inline href javascript: |
| CWE | CWE-79 — Improper Neutralization of Input |
| Vector | Adjunto SVG en chat, renderizado inline |
| Contexto de ejecución | DOM principal de la app, no sandboxed |
| Persistencia | Stored — el payload queda en la conversación |
| Interacción requerida | Click del usuario en el texto del SVG |
Notas técnicas
- Este reporte fue el origen de un parche que posteriormente fue bypassado en otro reporte. El parche sanitizaba el literal
javascript:enhrefde SVGs, pero sin normalizar entidades HTML. - El SVG se renderiza inline en el DOM principal — eso es lo que permite que el
javascript:tenga acceso al contexto de la página. Si se cargara como imagen externa o en un iframe sandboxed, el impacto sería diferente. - La diferencia técnica con el Self-XSS anterior consiste en una variante del payload que pasa donde el otro no, sugiriendo un filtro basado en strings en lugar de un parser real.
Labs relacionados
Practica Stored XSS en SVG y bypasses de filtros de sanitización: 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 en nombres de plantilla — del campo más aburrido al domain takeover
Un campo de título en una plantilla, sin sanitizar, en una sesión con permisos sobre dominios. Bounty real de €1.200. Cómo encontrar XSS donde nadie mira.
CSP Bypass — JSONP, base-uri, AngularJS gadgets, dangling markup
Content-Security-Policy roto con strict-dynamic + JSONP, sin base-uri, AngularJS sandbox escapes, JSON hijacking. Cómo escalar XSS cuando el CSP teóricamente bloquea.