Nivel IntermedioGratis

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.

Gorka El Bochi9 de mayo de 202611 min

Deja de leer sobre bugs y empieza a cazarlos

Sin tarjeta · Academy gratis · cancela cuando quieras

Crear cuenta gratis

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

xml
<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 atacanteEn el chat compartido
¿Quién queda expuesto?Solo el propio atacanteCualquier participante del chat
¿Persiste para otros?NoSí — el SVG está almacenado en la conversación
¿Afecta a agentes de soporte?NoSí — 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

less
[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

CampoValor
Tipo de vulnerabilidadStored XSS — SVG inline href javascript:
CWECWE-79 — Improper Neutralization of Input
VectorAdjunto SVG en chat, renderizado inline
Contexto de ejecuciónDOM principal de la app, no sandboxed
PersistenciaStored — el payload queda en la conversación
Interacción requeridaClick 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: en href de 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.

Gratis · sin cuenta

La checklist que repaso en cada objetivo nuevo

47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.

Te das de baja en un clic, desde cualquier correo.

Practica esto en un lab

Xss

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados

2.786

hunters entrenando

63

labs de hackeos reales

1.767

completaciones

$14.790

pagados por estos bugs

37 flags capturadas esta semana·Hackeos reales de HackerOne · YesWeHack · Bugcrowd·Sin permanencia·Academy gratis

La comunidad

Esto no es una plataforma con usuarios.Es gente rompiendo lo mismo que tú.

Dentro se pregunta, se enseña lo que se ha encontrado y se resuelve en grupo lo que atascaría a cualquiera solo. Se entra gratis: sin cuenta, sin plan y sin dejar el correo.

Entrar a la comunidad de BBLABS en Discord
Entrar al Discord

Es gratis y abierto. No hace falta tener cuenta en BBLABS para entrar.