Open WebUI: Stored XSS to Account Takeover via Model Profile Images
High7.6CVE-2026-54013 · Published Jun 17, 2026 · updated Jul 20, 2026
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| open-webui PyPI | < 0.9.6 | 0.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.
More Open WebUI advisories
All Open WebUI| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Jun 17 | Open WebUI IDOR: Calendar event re-parenting allows writing events into another user's calendar CVE-2026-54006Medium4.3fixed in 0.9.6 | Medium4.3 | 0.9.6 |
| Jun 17 | Open WebUI: Cross-origin postMessage confirmation bypass via action:submit CVE-2026-54007High6.5fixed in 0.9.6 | High6.5 | 0.9.6 |
| Jun 17 | Open WebUI: Redirect-Bypass SSRF in OAuth `_process_picture_url` (incomplete-fix sibling of CVE-2026-45401) CVE-2026-54008High8.5fixed in 0.9.6 | High8.5 | 0.9.6 |
| Jun 17 | Open WebUI: Cross-user file disclosure via /api/chat/completions image_url field CVE-2026-54009Medium6.5fixed in 0.9.6 | Medium6.5 | 0.9.6 |
| Jun 17 | Open WebUI: Forged chat-file link allows cross-user file read and deletion CVE-2026-54010High8.3fixed in 0.9.6 | High8.3 | 0.9.6 |
| Jun 17 | Open WebUI: Stored XSS in Mermaid Markdown Preview CVE-2026-54011High8.7fixed in 0.9.6 | High8.7 | 0.9.6 |