PT-2026-50480 · Pypi · Open-Webui
Published
2026-06-17
·
Updated
2026-06-17
·
CVE-2026-54008
CVSS v3.1
8.5
High
| Vector | AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N |
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:python
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 Foundr Location: 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
python
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.6References
- 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.Fix
SSRF
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Open-Webui