Severity by source
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Network reachable OAuth login flow, no UI, requires any IdP identity (PR:L); SSRF reaches systems beyond auth boundary (S:C); high read of internal data (C:H), limited integrity from stored profile field (I:L).
Primary rating from Vendor (https://github.com/open-webui/open-webui).
CVSS VectorVendor: https://github.com/open-webui/open-webui
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Lifecycle Timeline
3DescriptionCVE.org
Summary
backend/open_webui/utils/oauth.py::_process_picture_url (v0.9.5, lines 1435-1470) calls validate_url(picture_url) on the initial URL only, then invokes aiohttp.ClientSession.get(picture_url, ...) without allow_redirects=False. aiohttp's default is allow_redirects=True, max_redirects=10; the function does not pass the project's AIOHTTP_CLIENT_ALLOW_REDIRECTS env constant either. An attacker with a valid OAuth IdP identity can therefore submit a public URL that 302-redirects to an internal address and read the internal response body via the attacker's own profile_image_url field.
This is the same redirect-bypass class as CVE-2026-45401 (GHSA-rh5x-h6pp-cjj6), on a 6th call site that the v0.9.5 patch missed. CVE-2026-45401's advisory body enumerates exactly five affected paths — SafeWebBaseLoader._scrape, _fetch, get_content_from_url, load_url_image, get_image_base64_from_url — none in utils/oauth.py.
Vulnerable code (v0.9.5)
backend/open_webui/utils/oauth.py, lines 1435-1470:
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)
# initial URL only
get_kwargs = {}
if access_token:
get_kwargs['headers'] = {'Authorization': f'Bearer {access_token}'}
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.get(picture_url, **get_kwargs,
ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
# ^^^^^^^^^^^ no allow_redirects=False
if resp.ok:
picture = await resp.read()
base64_encoded_picture = base64.b64encode(picture).decode('utf-8')
guessed_mime_type = mimetypes.guess_type(picture_url)[0]
if guessed_mime_type is None:
guessed_mime_type = 'image/jpeg'
return f'data:{guessed_mime_type};base64,{base64_encoded_picture}'
...The function is invoked at oauth.py:1556 (new-user OAuth signup) and oauth.py:1536 (existing-user picture update on login). Neither call site re-validates after redirect-following.
backend/open_webui/retrieval/web/utils.py (v0.9.5) imports the env constant AIOHTTP_CLIENT_ALLOW_REDIRECTS at line 51 and uses it on the five paths patched by CVE-2026-45401. utils/oauth.py does not import or reference it.
Exploitation
Preconditions:
ENABLE_OAUTH_SIGNUP=trueorOAUTH_UPDATE_PICTURE_ON_LOGIN=true(common in production OAuth-IdP deployments)- Attacker has a valid identity on the configured OAuth IdP (Google, Microsoft, GitHub, or any generic OIDC provider)
Steps:
- Attacker hosts a redirect endpoint at
http://attacker.example/ron a public IP.validate_url("http://attacker.example/r")returns True (is_global=Truefor public IPs). - Attacker sets their IdP
pictureclaim tohttp://attacker.example/r. - Attacker signs in to open-webui via OAuth. open-webui invokes
_process_picture_url("http://attacker.example/r", ...). validate_urlaccepts the public URL.session.get("http://attacker.example/r")is invoked.- attacker.example responds
HTTP/1.1 302 Found\r\nLocation: http://127.0.0.1:11434/api/tags. (Orhttp://169.254.169.254/latest/meta-data/iam/security-credentials/, RFC1918 internal services, etc.) - aiohttp follows the redirect server-side. No re-validation.
- The internal response body is read into
picture, base64-encoded, and stored asprofile_image_url = "data:image/jpeg;base64,..."on the attacker's account. - Attacker reads back via
GET /api/v1/auths/. Decode the base64 payload to get the full internal response body.
Impact
Full-read SSRF, identical read-back primitive to CVE-2026-45338:
- Cloud metadata services (AWS IMDSv1 at
169.254.169.254, GCPmetadata.google.internal, Azure IMDS) → IAM credentials, managed-identity tokens - Localhost-bound services (Ollama at
:11434, Redis, Elasticsearch, internal Postgres exporters) - RFC1918 internal infrastructure not exposed to the internet
Distinction from prior CVEs
| Prior CVE | This finding | Distinguishing fact |
|---|---|---|
| CVE-2026-45338 (GHSA-24c9) | _process_picture_url had no validate_url() call at all | Fixed in v0.9.0 by adding the call. Ours is the call being insufficient because it doesn't loop over redirect targets. Different mechanism, different fix. |
| CVE-2026-45400 (GHSA-8w7q) | validate_url() had urlparse-vs-requests parser disagreement on \@ chars | Fixed in v0.9.5 by char-blocklist. Ours is post-validation redirect-following — orthogonal mechanism. |
| CVE-2026-45401 (GHSA-rh5x) | Five paths in retrieval, routers/images, utils/files, utils/middleware | Parent class. Same CWE-918 redirect-bypass mechanism. utils/oauth.py::_process_picture_url is not among the five paths in the parent advisory's "Affected code paths" section. Same class, missed sink. Direct sibling. |
Suggested fix
async with session.get(
picture_url,
**get_kwargs,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS,
# add
) as resp:Or, if redirects must remain enabled by default, wrap in a manual-follow loop that re-invokes validate_url() on each Location header. This mirrors the fix shape applied to the five paths in CVE-2026-45401.
Affected versions
Vulnerable: <= 0.9.5 Fix: 0.9.6
References
- CVE-2026-45401 / GHSA-rh5x-h6pp-cjj6 (parent cluster, redirect-bypass on 5 paths)
- CVE-2026-45338 / GHSA-24c9-2m8q-qhmh (original
_process_picture_urlSSRF, patched v0.9.0) - CVE-2026-45400 / GHSA-8w7q-q5jp-jvgx (
validate_urlparser-disagreement bypass, patched v0.9.5) - open-webui issue #24560 (corroborates that the v0.9.5 redirect-fix was applied piecemeal across call sites)
Proof of Concept
End-to-end PoC executed against ghcr.io/open-webui/open-webui:v0.9.5 in Docker compose. Three services: attacker (OIDC IdP + 302-redirect endpoint on evil.example.com:9001/redirect), canary (internal target on internal-target.local:9002/sentinel), open-webui v0.9.5.
Fresh-CSPRNG sentinel generated after OAuth state-establishing call (per Gate 5.5 oracle protocol): SSRF-POC-5580111b2a0d7d0c8324bfa92a0d9d09.
Result:
profile_image_urlfield after OAuth login:data:image/jpeg;base64,U1NSRi1QT0MtNTU4MDExMWIyYTBkN2QwYzgzMjRiZmE5MmEwZDlkMDk=- Base64 decode:
SSRF-POC-5580111b2a0d7d0c8324bfa92a0d9d09(byte-for-byte sentinel match) - Canary log:
!!! SSRF HIT - sentinel served
Chain confirmed: OAuth login → IdP returns picture claim evil.example.com:9001/redirect → validate_url() accepts FQDN → aiohttp.ClientSession.get(...) follows 302 to internal-target.local:9002/sentinel server-side without re-validation → response body base64-encoded into attacker's profile_image_url → readable via GET /api/v1/auths/.
PoC artifacts (compose, attacker server, canary, run/verify scripts, full transcript) available on request.
Reporter
Matteo Panzeri — GitHub: matte1782, contact: matteo1782@gmail.com. Requesting CVE credit as Matteo Panzeri.
Articles & Coverage 1
AnalysisAI
Server-side request forgery in Open WebUI versions 0.9.5 and earlier allows authenticated OAuth users to read arbitrary internal HTTP responses by abusing the _process_picture_url function in backend/open_webui/utils/oauth.py, which validates only the initial URL and then permits aiohttp's default 10-redirect follow chain to reach internal addresses. The decoded response body is stored in the attacker's profile_image_url and retrievable via GET /api/v1/auths/, yielding cloud metadata credentials and access to localhost-bound services. Publicly available exploit code exists (detailed sentinel-verified PoC supplied by the reporter); no public exploit identified at time of analysis in the form of weaponized tooling, and the CVE is not on the CISA KEV list.
Technical ContextAI
The root cause is CWE-918 Server-Side Request Forgery manifesting as an incomplete-fix sibling of CVE-2026-45401. Open WebUI uses Python's aiohttp library, whose ClientSession.get() defaults to allow_redirects=True with max_redirects=10. The project introduced an AIOHTTP_CLIENT_ALLOW_REDIRECTS env constant and applied it to five fetch sinks in retrieval/web/utils.py, but the OAuth profile-picture sink at utils/oauth.py:1435-1470 was missed: it calls validate_url() on the initial URL only, then issues session.get(picture_url, ssl=AIOHTTP_CLIENT_SESSION_SSL) with no allow_redirects=False and no per-hop revalidation. Because validate_url treats public hosts as safe (is_global=True), an attacker-controlled HTTP 302 to an RFC1918 address, 127.0.0.1, or cloud-metadata endpoints such as 169.254.169.254 is followed server-side. CPE pkg:pip/open-webui confirms the affected distribution is the Python package shipped via PyPI and the ghcr.io/open-webui/open-webui container image.
RemediationAI
Vendor-released patch: upgrade open-webui to version 0.9.6 or later (pip/PyPI and the corresponding ghcr.io/open-webui/open-webui image tag), per advisory GHSA-226f-f24g-524w at https://github.com/open-webui/open-webui/security/advisories/GHSA-226f-f24g-524w. If immediate upgrade is not possible, set ENABLE_OAUTH_SIGNUP=false to block attacker self-onboarding and OAUTH_UPDATE_PICTURE_ON_LOGIN=false to stop the picture-update code path from firing on existing-user login - the trade-off is that new OAuth-only signups must be provisioned manually and existing users will not see refreshed avatars. As a network-layer compensating control, place the Open WebUI backend behind an egress proxy that blocks outbound HTTP to RFC1918, loopback, link-local (169.254.0.0/16 including 169.254.169.254), and cloud-metadata FQDNs such as metadata.google.internal; the side effect is that legitimate avatar URLs hosted on internal CDNs will also fail. The reporter also documents a code-level workaround: patch _process_picture_url to pass allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS or wrap the call in a manual-follow loop that re-runs validate_url() on every Location header.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing
Same weakness CWE-918 – Server-Side Request Forgery (SSRF)
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-38534
GHSA-226f-f24g-524w