CVE-2026-54018High· 7.7▾ TwilightOpen WebUI: SSRF Protection Bypass in Playwright Web Loader via HTTP Redirects
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 42.4 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 4.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
Last analysed / modified upstream
0.3%
0.3% → 0.4%
The SafePlaywrightURLLoader implements a validate_url function to prevent SSRF attacks by checking the IP address of the user-provided URL. However, this validation is performed only on the initial URL.
Since Playwright automatically follows HTTP redirects (301/302) by default, an attacker can bypass the validation by providing a safe URL that redirects to a restricted internal network address (e.g., localhost, Docker container network, or Cloud Metadata).
This allows the application to access internal services despite ENABLE_RAG_LOCAL_WEB_FETCH being set to False
Root Cause
The application validates the initial user-provided URL using self._safe_process_url_sync(url). This correctly resolves the domain and ensures it does not point to a private IP.
The application then calls page.goto(url). By default, Playwright automatically follows HTTP redirects (301/302).
The Bypass: If the destination server returns a redirect to an internal IP (e.g., 127.0.0.1 or 169.254.169.254), the browser follows it without re-validating the new destination. The initial validation is bypassed because it only checked the first URL, not the entire redirect chain.
for url in self.urls:
try:
self._safe_process_url_sync(url)
page = browser.new_page()
response = page.goto(url, timeout=self.playwright_timeout) #this
if response is None:
raise ValueError(...)
text = self.evaluator.evaluate(page, browser, response)
(This PoC uses Docker to easily demonstrate internal network access (accessing a container by service name). However, the vulnerability is NOT tied to Docker.)
# attack_server.py
from flask import Flask, redirect
app = Flask(__name__)
@app.route('/')
def attack():
# Redirect to the Open WebUI container's internal port
return redirect("http://open-webui:8080/api/version", code=302)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
<img width="580" height="192" alt="image" src="https://github.com/user-attachments/assets/4600dbb5-a81d-4e58-b787-afe04fe59d6e" />
The Playwright browser follows the redirect to the internal address (http://open-webui:8080/api/version)
implement a request interceptor using Playwright's page.route. This ensures all requests, including redirects, are validated before connection.
apply the following logic to both lazy_load and alazy_load methods:
# async context
async def intercept_route(route):
try:
await run_in_threadpool(validate_url, route.request.url)
await route.continue_()
except Exception:
await route.abort()
await page.route("**/*", intercept_route)
response = await page.goto(url, timeout=self.playwright_timeout)
open-webui <= 0.9.5Upgrade to a patched release:
open-webui 0.9.6Connected by shared product, vendor, weakness, or advisory.
CVE-2026-59221High· 7.7open-webui terminal proxy path traversal guard bypass via 9x encoded traversal
CVE-2026-34225Medium· 4.3Open WebUI has Blind Server Side Request Forgery in its Image Edit Functionality
CVE-2026-54008High· 8.5Open WebUI: Redirect-Bypass SSRF in OAuth `_process_picture_url` (incomplete-fix sibling of CVE-2026-45401)
CVE-2026-54017High· 7.7Open WebUI: Path traversal / SSRF in terminal server proxy via encoded path traversal
CVE-2026-59223Medium· 4.3Open WebUI: `WEB_FETCH_FILTER_LIST` host allow/block filter bypassable via URL path and non-label-boundary matching
CVE-2026-59224High· 8.0Open WebUI: Terminal proxy forwards a spoofable, integrity-unbound user identity to the upstream (X-User-Id header and ws_terminal session_id query injection)