PYSEC-2026-2708

Vulnerability from pysec - Published: 2026-07-13 15:19 - Updated: 2026-07-13 16:05
VLAI
Details

Summary

The profile_image_url field on the user profile update form accepted arbitrary data: URI values without MIME-type validation. Two distinct attack paths were independently demonstrated by separate reporters:

  1. data:text/html;base64,... in a new browser tab (raresvis, 2025-04-17) — when a victim right-clicks a user's profile picture and chooses "Open image in new tab", the browser navigates to the data: URL and executes embedded scripts in the data: origin. Limited to social-engineering / redirect attacks because the script does not run in the application origin.

  2. data:image/svg+xml;base64,... re-served by the application origin (Gh05t666nero, 2026-01-09) — GET /api/v1/users/{user_id}/profile/image decoded the base64 and returned StreamingResponse(media_type=<user-controlled>) extracted from the data: header. With media_type=image/svg+xml and Content-Disposition: inline, the SVG-embedded scripts executed in the application origin, enabling JWT theft from localStorage and full account takeover of any user — including admins — who loaded the malicious profile image URL.

Both attack paths share the same root cause (lack of MIME-type validation on profile_image_url) and are closed by the same fix.

Vulnerable code (v0.7.0)

backend/open_webui/routers/users.py get_user_profile_image_by_id():

elif user.profile_image_url.startswith("data:image"):
    header, base64_data = user.profile_image_url.split(",", 1)
    image_data = base64.b64decode(base64_data)
    image_buffer = io.BytesIO(image_data)
    media_type = header.split(";")[0].lstrip("data:")  # user-controlled
    return StreamingResponse(
        image_buffer,
        media_type=media_type,
        headers={"Content-Disposition": "inline"},
    )

Fix

Commit 773787c74 (2026-02-11), first contained in tag v0.8.0, applies the validate_profile_image_url field validator to every form that accepts profile_image_url (UserModel, UpdateProfileForm, SignupForm in backend/open_webui/models/users.py and backend/open_webui/models/auths.py). The validator explicitly rejects data:image/svg+xml and any non-image data URI, allowing only data:image/{png,jpeg,gif,webp};base64 plus known internal paths and http(s):// URLs. This blocks both attack vectors at form submission time, so a malicious URL can no longer be persisted to the database.

Credits

  • raresvis — discovered the data:text/html-via-new-tab path
  • Gh05t666nero — discovered the data:image/svg+xml-via-server-side path (the more severe origin-XSS vector that determined the consolidated CVSS)

Per our Report Handling policy, the cluster is consolidated into the earliest filing with credit to every reporter who demonstrated a distinct exploitation path.

Affected / patched versions

  • Affected: < 0.8.0
  • Patched: >= 0.8.0
Impacted products
Name purl
open-webui pkg:pypi/open-webui

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "open-webui",
        "purl": "pkg:pypi/open-webui"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "0.1.124",
        "0.1.125",
        "0.2.0",
        "0.2.1",
        "0.2.2",
        "0.2.3",
        "0.2.4",
        "0.2.5",
        "0.3.0",
        "0.3.1",
        "0.3.10",
        "0.3.12",
        "0.3.13",
        "0.3.14",
        "0.3.15",
        "0.3.16",
        "0.3.17",
        "0.3.17.dev2",
        "0.3.17.dev3",
        "0.3.17.dev4",
        "0.3.17.dev5",
        "0.3.18",
        "0.3.19",
        "0.3.2",
        "0.3.20",
        "0.3.21",
        "0.3.22",
        "0.3.23",
        "0.3.24",
        "0.3.25",
        "0.3.26",
        "0.3.27",
        "0.3.27.dev1",
        "0.3.27.dev2",
        "0.3.27.dev3",
        "0.3.28",
        "0.3.29",
        "0.3.3",
        "0.3.30",
        "0.3.30.dev1",
        "0.3.30.dev2",
        "0.3.31",
        "0.3.31.dev1",
        "0.3.32",
        "0.3.33",
        "0.3.33.dev1",
        "0.3.34",
        "0.3.35",
        "0.3.4",
        "0.3.5",
        "0.3.6",
        "0.3.7",
        "0.3.8",
        "0.3.9",
        "0.4.0",
        "0.4.0.dev1",
        "0.4.0.dev2",
        "0.4.1",
        "0.4.2",
        "0.4.3",
        "0.4.4",
        "0.4.5",
        "0.4.6",
        "0.4.6.dev1",
        "0.4.7",
        "0.4.8",
        "0.5.0",
        "0.5.0.dev1",
        "0.5.0.dev2",
        "0.5.1",
        "0.5.10",
        "0.5.11",
        "0.5.12",
        "0.5.13",
        "0.5.14",
        "0.5.15",
        "0.5.16",
        "0.5.17",
        "0.5.18",
        "0.5.19",
        "0.5.2",
        "0.5.20",
        "0.5.3",
        "0.5.3.dev1",
        "0.5.4",
        "0.5.5",
        "0.5.6",
        "0.5.7",
        "0.5.8",
        "0.5.9",
        "0.6.0",
        "0.6.1",
        "0.6.10",
        "0.6.11",
        "0.6.12",
        "0.6.13",
        "0.6.14",
        "0.6.15",
        "0.6.16",
        "0.6.18",
        "0.6.19",
        "0.6.2",
        "0.6.20",
        "0.6.21",
        "0.6.22",
        "0.6.23",
        "0.6.24",
        "0.6.25",
        "0.6.26",
        "0.6.26.dev1",
        "0.6.27",
        "0.6.28",
        "0.6.29",
        "0.6.3",
        "0.6.30",
        "0.6.31",
        "0.6.32",
        "0.6.33",
        "0.6.34",
        "0.6.35",
        "0.6.36",
        "0.6.37",
        "0.6.38",
        "0.6.39",
        "0.6.4",
        "0.6.40",
        "0.6.41",
        "0.6.42",
        "0.6.43",
        "0.6.5",
        "0.6.6",
        "0.6.6.dev1",
        "0.6.7",
        "0.6.8",
        "0.6.9",
        "0.7.0",
        "0.7.1",
        "0.7.2"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45299",
    "GHSA-6gh2-q7cp-9qf6"
  ],
  "details": "## Summary\n\nThe `profile_image_url` field on the user profile update form accepted arbitrary `data:` URI values without MIME-type validation. Two distinct attack paths were independently demonstrated by separate reporters:\n\n1. **`data:text/html;base64,...` in a new browser tab** (raresvis, 2025-04-17) \u2014 when a victim right-clicks a user\u0027s profile picture and chooses \"Open image in new tab\", the browser navigates to the data: URL and executes embedded scripts in the `data:` origin. Limited to social-engineering / redirect attacks because the script does not run in the application origin.\n\n2. **`data:image/svg+xml;base64,...` re-served by the application origin** (Gh05t666nero, 2026-01-09) \u2014 `GET /api/v1/users/{user_id}/profile/image` decoded the base64 and returned `StreamingResponse(media_type=\u003cuser-controlled\u003e)` extracted from the `data:` header. With `media_type=image/svg+xml` and `Content-Disposition: inline`, the SVG-embedded scripts executed in the **application origin**, enabling JWT theft from `localStorage` and full account takeover of any user \u2014 including admins \u2014 who loaded the malicious profile image URL.\n\nBoth attack paths share the same root cause (lack of MIME-type validation on `profile_image_url`) and are closed by the same fix.\n\n## Vulnerable code (v0.7.0)\n\n`backend/open_webui/routers/users.py` `get_user_profile_image_by_id()`:\n\n```python\nelif user.profile_image_url.startswith(\"data:image\"):\n    header, base64_data = user.profile_image_url.split(\",\", 1)\n    image_data = base64.b64decode(base64_data)\n    image_buffer = io.BytesIO(image_data)\n    media_type = header.split(\";\")[0].lstrip(\"data:\")  # user-controlled\n    return StreamingResponse(\n        image_buffer,\n        media_type=media_type,\n        headers={\"Content-Disposition\": \"inline\"},\n    )\n```\n\n## Fix\n\nCommit `773787c74` (2026-02-11), first contained in tag **v0.8.0**, applies the `validate_profile_image_url` field validator to every form that accepts `profile_image_url` (`UserModel`, `UpdateProfileForm`, `SignupForm` in `backend/open_webui/models/users.py` and `backend/open_webui/models/auths.py`). The validator explicitly rejects `data:image/svg+xml` and any non-image data URI, allowing only `data:image/{png,jpeg,gif,webp};base64` plus known internal paths and `http(s)://` URLs. This blocks both attack vectors at form submission time, so a malicious URL can no longer be persisted to the database.\n\n## Credits\n\n- **raresvis** \u2014 discovered the `data:text/html`-via-new-tab path\n- **Gh05t666nero** \u2014 discovered the `data:image/svg+xml`-via-server-side path (the more severe origin-XSS vector that determined the consolidated CVSS)\n\nPer our Report Handling policy, the cluster is consolidated into the earliest filing with credit to every reporter who demonstrated a distinct exploitation path.\n\n## Affected / patched versions\n\n- Affected: `\u003c 0.8.0`\n- Patched: `\u003e= 0.8.0`",
  "id": "PYSEC-2026-2708",
  "modified": "2026-07-13T16:05:11.296373Z",
  "published": "2026-07-13T15:19:05.784784Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-6gh2-q7cp-9qf6"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45299"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/open-webui/open-webui"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/releases/tag/v0.8.0"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/open-webui"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-6gh2-q7cp-9qf6"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Open WebUI has Stored Cross-Site Scripting In Profile Picture"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…