# Open WebUI: Stored XSS via unescaped KaTeX render-error fallback in rendered messages

Open WebUI's KaTeX error fallback inserted raw math source via {@html}; a stack-overflowing math block with an img onerror tag yields stored XSS and session-token theft.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: maxntv
- Disclosed: 2026-08-04
- CVE: CVE-2026-70492
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-pwxh-7358-jq2x
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pwxh-7358-jq2x
- Tags: area:web domain:web impact:ato impact:data-leak impact:privesc kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:xss

**Target:** Svelte chat message renderer (KatexRenderer.svelte) in Open WebUI web app
**Root cause:** KaTeX was called with throwOnError:false, which suppresses parse errors but not RangeError from deeply nested input; the catch branch assigned the untouched math source to a value inserted with {@html}, and the Markdown math tokenizer captures delimiters' contents verbatim including tags.
**Steps:**
1. Craft a message with one inline math block: `$`, 100000 `{`, `<img src=x onerror=alert(document.domain)>`, 100000 `}`, `$` (generated via `python3 -c 'N=100000; print("$"+"{"*N+"<img src=x onerror=alert(document.domain)>"+"}"*N+"$")'`)
2. Store it via a chat message or any endpoint writing message content
3. Get a victim (e.g. admin) to open the chat, shared chat link, or channel
4. KaTeX overflows the stack, catch fallback inserts raw source as HTML, onerror fires
5. Replace alert() with a request exfiltrating `localStorage.token` to an attacker host
**How found:** Reporter noticed the KaTeX catch branch treated any failure as 'show original formula' and fed it to {@html}; realized throwOnError:false only suppresses parse errors, so deeply nested braces could force a RangeError stack overflow and route attacker-controlled markup through the unescaped error path.
**Exploitation:** Attacker with a normal account stores the crafted math block in a message, then lures a viewer (demonstrated against an administrator opening a shared chat) to render it; the unescaped fallback executes script on the Open WebUI origin, and a request carrying localStorage.token exfiltrates the session token for account takeover.
**Escalation:** Viewer being an administrator turns the XSS into full instance admin access; shared chats and channels widen reach to any user who opens the content.
**Insight:** Error/fallback paths that echo user input are a separate sink from the happy path: check whether a library's error-suppression flag (throwOnError:false) covers only parse errors, and whether the catch branch re-inserts raw source into an HTML sink like {@html}.

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

**Python one-liner generating the KaTeX overflow XSS payload**

```
python3 -c 'N=100000; print("$"+"{"*N+"<img src=x onerror=alert(document.domain)>"+"}"*N+"$")'
```

