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.

Free · no account

The checklist I run on every new target

47 checks ordered by cost: first what can get you in trouble, then the cheap stuff, and finally the expensive stuff — which is where the big bounties are. I'll send it to your inbox right now.

Unsubscribe in one click, from any email.

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

2,482

hunters training

62

labs from real hacks

1,629

completions

$14,790

paid out for these bugs

11 flags captured this week·Real hacks from HackerOne · YesWeHack · Bugcrowd·No commitment·Free Academy