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
<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 session | In the shared chat |
| Who is exposed? | Only the attacker | Any chat participant |
| Does it persist for others? | No | Yes — the SVG is stored in the conversation |
| Does it affect support agents? | No | Yes — 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
[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
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
| Field | Value |
|---|---|
| Vulnerability type | Stored XSS — SVG inline href javascript: |
| CWE | CWE-79 — Improper Neutralization of Input |
| Vector | SVG attachment in chat, rendered inline |
| Execution context | The app's main DOM, not sandboxed |
| Persistence | Stored — the payload stays in the conversation |
| Required interaction | User 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 thehrefof 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.
Related labs
Practice Stored XSS in SVG and sanitization filter bypasses: XSS labs.
Practice this in a lab
Xss
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
Stored XSS €1200 — sanitizer bypass via SVG href javascript: with entity encoding
Walkthrough of a real €1200 report: stored XSS in POE bypassing a sanitizer fix via SVG with href=`javascript:` and HTML entity encoding over the filtered characters.
Stored XSS in template names — from the most boring field to domain takeover
A template title field, unsanitized, in a session with permissions over domains. A real €1,200 bounty. How to find XSS where nobody looks.
CSP Bypass — JSONP, base-uri, AngularJS gadgets, dangling markup
Content-Security-Policy broken with strict-dynamic + JSONP, missing base-uri, AngularJS sandbox escapes, JSON hijacking. How to escalate XSS when the CSP theoretically blocks it.