CVE-2026-54008: Open WebUI: Redirect-Bypass SSRF in OAuth `_process_picture_url`
Summary
backend/openwebui/utils/oauth.py::processpictureurl (v0.9.5, lines 1435-1470) calls validateurl(pictureurl) on the initial URL only, then invokes aiohttp.ClientSession.get(pictureurl, ...) without allowredirects=False. aiohttp's default is allowredirects=True, maxredirects=10; the function does not pass the project's AIOHTTPCLIENTALLOWREDIRECTS 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 profileimageurl 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, getcontentfromurl, loadurlimage, getimagebase64fromurl — none in utils/oauth.py.
Vulnerable code (v0.9.5)
backend/openwebui/utils/oauth.py, lines 1435-1470:
python async def processpictureurl(self, pictureurl: str, accesstoken: str = None) -> str: if not pictureurl: return '/user.png' try: validateurl(pictureurl) # initial URL only
getkwargs = {} if accesstoken: getkwargs['headers'] = {'Authorization': f'Bearer {accesstoken}'} async with aiohttp.ClientSession(trustenv=True) as session: async with session.get(pictureurl, getkwargs, ssl=AIOHTTPCLIENTSESSIONSSL) as resp: # ^^^^^^^^^^^ no allowredirects=False if resp.ok: picture = await resp.read() base64encodedpicture = base64.b64encode(picture).decode('utf-8') guessedmimetype = mimetypes.guesstype(pictureurl)[0] if guessedmimetype is None: guessedmimetype = 'image/jpeg' return f'data:{guessedmimetype};base64,{base64encodedpicture}' ...
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/openwebui/retrieval/web/utils.py (v0.9.5) imports the env constant AIOHTTPCLIENTALLOWREDIRECTS 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: - ENABLEOAUTHSIGNUP=true or OAUTHUPDATEPICTUREONLOGIN=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:
1. Attacker hosts a redirect endpoint at http://attacker.example/r on a public IP. validateurl("http://attacker.example/r") returns True (isglobal=True for public IPs). 2. Attacker sets their IdP picture claim to http://attacker.example/r. 3. Attacker signs in to open-webui via OAuth. open-webui invokes processpictureurl("http://attacker.example/r", ...). 4. validateurl accepts the public URL. session.get("http://attacker.example/r") is invoked. 5. attacker.example responds HTTP/1.1 302 Found\r\nLocation: http://127.0.0.1:11434/api/tags. (Or http://169.254.169.254/latest/meta-data/iam/security-credentials/, RFC1918 internal services, etc.) 6. aiohttp follows the redirect server-side. No re-validation. 7. The internal response body is read into picture, base64-encoded, and stored as profileimageurl = "data:image/jpeg;base64,..." on the attacker's account. 8. 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, GCP metadata.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) | processpictureurl had no validateurl() 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) | validateurl() 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::processpictureurl 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( pictureurl, getkwargs, ssl=AIOHTTPCLIENTSESSIONSSL, allowredirects=AIOHTTPCLIENTALLOWREDIRECTS, # add ) as resp:
Or, if redirects must remain enabled by default, wrap in a manual-follow loop that re-invokes validateurl() 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 processpictureurl SSRF, patched v0.9.0) - CVE-2026-45400 / GHSA-8w7q-q5jp-jvgx (validateurl parser-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: - profileimageurl field 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 → validateurl() 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 profileimageurl → 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.
Other sources
Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. Prior to 0.9.6, backend/openwebui/utils/oauth.py::processpictureurl calls validateurl(pictureurl) on the initial URL only, then invokes aiohttp.ClientSession.get(pictureurl, ...) without allowredirects=False. aiohttp's default is allowredirects=True, maxredirects=10; the function does not pass the project's AIOHTTPCLIENTALLOWREDIRECTS 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 profileimageurl field. This vulnerability is fixed in 0.9.6.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/open-webuito a version that resolves this vulnerability.Fixed in 0.9.6 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 0.9.6 - Configuration
In backend/open_webui/utils/oauth.py::_process_picture_url, ensure aiohttp is called with allow_redirects=False (or otherwise prevent automatic 302/Location redirect-following) instead of aiohttp default allow_redirects=True, so the internal redirect target is not fetched server-side.
open-webui backend (oauth picture fetch) aiohttp.ClientSession.get allow_redirects = false - Configuration
If redirects must remain enabled, implement a manual-follow loop in backend/open_webui/utils/oauth.py::_process_picture_url that extracts each Location header and re-invokes validate_url() on every redirect target before performing the next aiohttp request.
open-webui backend (oauth picture fetch) redirect handling (validate_url re-invocation on Location) = re-validate each redirect target
Event History
Frequently Asked Questions
What is the severity of CVE-2026-54008?
CVE-2026-54008 has a high severity score of 8.5.
What type of vulnerability is CVE-2026-54008?
CVE-2026-54008 is classified as a Server-Side Request Forgery (SSRF) vulnerability.
How do I fix CVE-2026-54008?
To fix CVE-2026-54008, ensure that the `aiohttp.ClientSession.get` call has `allow_redirects=False`.
What component is affected by CVE-2026-54008?
CVE-2026-54008 affects the `backend/open_webui/utils/oauth.py` module in version 0.9.5 of the open-webui software.
When was CVE-2026-54008 published?
CVE-2026-54008 was published on June 17, 2026.