Skip to content
Open WebUIGHSA-v2qm-5wxj-qhj7

Open WebUI: Stored XSS to Account Takeover via Model Profile Images

High7.6CVE-2026-54013 · Published Jun 17, 2026 · updated Jul 20, 2026

GitHub advisory

Affected versions

PackageAffectedFixed in
open-webui
PyPI
< 0.9.60.9.6
Details and references

# Stored XSS to Account Takeover via Model Profile Images in Open WebUI **Affected:** Open WebUI <= 0.9.5 **Bypass of:** GHSA-3wgj-c2hg-vm6q, GHSA-3856-3vxq-m6fc --- ## TL;DR Open WebUI patched SVG XSS in user profile images and webhook profile images but forgot to apply the same fix to **model** profile images. The `ModelMeta` class has no `validate_profile_image_url` field validator, and the model image serving endpoint has no MIME allowlist or `nosniff` header. Any authenticated user with `workspace.models` permission (enabled by default) can store a `data:image/svg+xml;base64,...` payload in a model's profile image and achieve full account takeover of anyone who navigates to the image URL. --- ## Past of the issue In early 2025, two security advisories landed for Open WebUI: - **GHSA-3wgj-c2hg-vm6q** SVG XSS via user profile images - **GHSA-3856-3vxq-m6fc** SVG XSS via webhook profile images The patches were clean. A `validate_profile_image_url` function was introduced in `backend/open_webui/utils/validate.py` a compiled regex that restricts `data:` URIs to safe raster formats (`image/png`, `image/jpeg`, `image/gif`, `image/webp`), explicitly excluding `image/svg+xml` because SVG can carry embedded `<script>` tags. On the output side, `users.py` added a MIME allowlist check and `X-Content-Type-Options: nosniff`. The fix was applied to `UserUpdateForm`, `UpdateProfileForm`, and later to `ChannelWebhookForm`. Three models patched. Case closed. Except there was a fourth endpoint. ## The Gap Open WebUI has a concept of "Models" user-created model configurations with metadata including a profile image. The metadata lives in `ModelMeta`: ```python # backend/open_webui/models/models.py, line 37-47 class ModelMeta(BaseModel): profile_image_url: Optional[str] = '/static/favicon.png' description: Optional[str] = None capabilities: Optional[dict] = None model_config = ConfigDict(extra='allow') ``` No `@field_validator`. No import of `validate_profile_image_url`. `ModelMeta` accepts any string as `profile_image_url` including `data:image/svg+xml;base64,...`. The serving endpoint at `GET /api/v1/models/model/profile/image` has the same gap: ```python # backend/open_webui/routers/models.py, line 503-518 elif profile_image_url.startswith('data:image'): header, base64_data = profile_image_url.split(',', 1) image_data = base64.b64decode(base64_data) image_buffer = io.BytesIO(image_data) media_type = header.split(';')[0].lstrip('data:') headers = {'Content-Disposition': 'inline'} # ... return StreamingResponse( image_buffer, media_type=media_type, headers=headers, ) ``` No MIME allowlist. No `nosniff`. No CSP. The SVG is served inline with `Content-Type: image/svg+xml` on the application's origin. Compare this with the **patched** user endpoint: ```python # backend/open_webui/routers/users.py, line 497-509 media_type = header.split(';')[0].lstrip('data:').lower() if media_type not in PROFILE_IMAGE_ALLOWED_MIME_TYPES: # <-- ABSENT in models.py return FileResponse(f'{STATIC_DIR}/user.png') return StreamingResponse( image_buffer, media_type=media_type, headers={ 'Content-Disposition': 'inline', 'X-Content-Type-Options': 'nosniff', # <-- ABSENT in models.py }, ) ``` The fix exists. It just was never applied here. ## Comparison Table | Endpoint | Input Validation | MIME Allowlist | nosniff | Status | |----------|:---:|:---:|:---:|--------| | `GET /users/{id}/profile/image` | YES | YES | YES | **Patched** | | `GET /webhooks/{id}/profile/image` | YES | no | no | Partially patched | | `GET /models/model/profile/image` | **NO** | **NO** | **NO** | **Vulnerable** | ## Three Write Vectors The malicious SVG data URI can be injected through any of three endpoints all pass `ModelForm` containing `ModelMeta` without validation: 1. **`POST /api/v1/models/create`** (line 195) any user with `workspace.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N
Severity from
GitHub (reviewed advisory)
Weakness
CWE-116, CWE-693, CWE-79
Also known as
CVE-2026-54013, PYSEC-2026-2757

More Open WebUI advisories

All Open WebUI
DateAdvisory
Jun 17Open WebUI IDOR: Calendar event re-parenting allows writing events into another user's calendar
CVE-2026-54006Medium4.3fixed in 0.9.6
Jun 17Open WebUI: Cross-origin postMessage confirmation bypass via action:submit
CVE-2026-54007High6.5fixed in 0.9.6
Jun 17Open WebUI: Redirect-Bypass SSRF in OAuth `_process_picture_url` (incomplete-fix sibling of CVE-2026-45401)
CVE-2026-54008High8.5fixed in 0.9.6
Jun 17Open WebUI: Cross-user file disclosure via /api/chat/completions image_url field
CVE-2026-54009Medium6.5fixed in 0.9.6
Jun 17Open WebUI: Forged chat-file link allows cross-user file read and deletion
CVE-2026-54010High8.3fixed in 0.9.6
Jun 17Open WebUI: Stored XSS in Mermaid Markdown Preview
CVE-2026-54011High8.7fixed in 0.9.6

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.