Intermediate levelFree

Stored XSS via SVG with href javascript: in chat — reclassification of Self-XSS

An SVG payload uploaded as an attachment. Filter bypassed. Rendered inline in the main context. Any chat participant is exposed on click.

Gorka El BochiMay 9, 202611 min

Quick answer

A conversational AI platform allowed attaching SVGs in the chat. A previous patch blocked javascript: in the href of an SVG, but a variant of the payload managed to bypass it. Because the SVG was rendered inline in the main DOM, any chat participant who clicked saw JavaScript execute in the app's context, not just the attacker. Reclassification of Self-XSS → multi-user impact Stored XSS.


Context: original classification and why it was reclassified

The report was initially classified as a duplicate of a previous Self-XSS (a variant that only affects the attacker and is therefore considered low impact).

The reclassification rests on two fundamental differences:

  • It bypasses the filter that neutralized the previous report's payload.
  • The SVG renders in the shared chat, so any other participant in the conversation is exposed to the payload with no more action than opening the chat.

That makes it Stored XSS with third-party reach, not Self-XSS.


The 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>

The SVG is uploaded as an attachment in any chat. The platform renders it inline in the interface. The <text> element appears as normal text on screen. On click, alert(document.domain) executes in the main domain's context.

The payload uses the javascript: scheme in the href attribute of an <a> element inside the SVG — the same vector as the previous report, but with an encoding or structure that gets past the filter that blocked the original.


Why it's not Self-XSS

The key distinction between Self-XSS and Stored XSS in this case is the scope of storage:

Self-XSS (previous variant)This report
Where does it execute?Only in the attacker's sessionIn the shared chat
Who is exposed?Only the attackerAny chat participant
Does it persist for others?NoYes — the SVG is stored in the conversation
Does it affect support agents?NoYes — if they open the chat

The payload is stored on the server within the conversation. Any user with access to that chat — guest, participant or support agent — sees the rendered SVG and is exposed to the payload.


CSP and inline rendering

The report indicates that the payload does not execute from the CDN and that it bypasses the CSP. This points to the platform rendering the SVG directly in the main document's DOM instead of loading it as an external resource from a CDN.

When the SVG is rendered inline in the main document's DOM, it inherits the app's execution context. The javascript: in an href of an inline SVG is not blocked by the CSP if the click event is triggered by the user, since it's not an external script but a navigation to a javascript: URI.


Attack flow

less
[Atacante] sube SVG con <a href="javascript:..."> en un chatServidor almacena el SVG en la conversación, filtro no lo bloqueaSVG renderizado inline en el chat UI[Atacante] comparte el link del chat (o un agente de soporte lo abre)
   ↓
[Víctima] abre el chatSVG visible con texto clickable[Víctima] hace click en el textojavascript: ejecutado en el contexto del dominio principal

Observed impact

  • The SVG is stored in the chat and persists for all participants.
  • JavaScript execution occurs in the main domain's context, with access to document.domain.
  • The payload bypasses both the SVG sanitization filter and the active CSP.
  • Support agents who open the conversation are equally exposed.

Technical classification

FieldValue
Vulnerability typeStored XSS — SVG inline href javascript:
CWECWE-79 — Improper Neutralization of Input
VectorSVG attachment in chat, rendered inline
Execution contextThe app's main DOM, not sandboxed
PersistenceStored — the payload stays in the conversation
Required interactionUser click on the SVG's text

Technical notes

  • This report was the origin of a patch that was later bypassed in another report. The patch sanitized the literal javascript: in the href of SVGs, but without normalizing HTML entities.
  • The SVG is rendered inline in the main DOM — that's what allows the javascript: to have access to the page's context. If it were loaded as an external image or in a sandboxed iframe, the impact would be different.
  • The technical difference from the previous Self-XSS is a variant of the payload that gets past where the other didn't, suggesting a string-based filter rather than a real parser.

Practice Stored XSS in SVG and sanitization filter bypasses: XSS labs.

Practice this in a lab

Xss

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy