Open WebUI: SSRF Protection Bypass in Playwright Web Loader via HTTP Redirects
Description
Summary
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
Details
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)
### PoC (This PoC uses Docker to easily demonstrate internal network access (accessing a container by service name). However, the vulnerability is NOT tied to Docker.)
- Ensure the Open WebUI is configured with the following environment variables. The vulnerability is specific to the Playwright engine.
- ENABLE_RAG_LOCAL_WEB_FETCH=False (Default)
- RAG_WEB_LOADER_ENGINE=playwright
- Setup and run attack server
- In Open WebUI, use the "Web Search" or "URL Loader" feature.
- Input the attacker's URL (e.g., http://attacker-ip/).
# 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)
The Playwright browser follows the redirect to the internal address (http://open-webui:8080/api/version)
### Impact + Cloud Environments: Access to Instance Metadata Service (IMDS) to steal cloud credentials. + Intranet/On-Premise: Scanning internal networks and accessing unauthenticated internal tools. + Container Environments: Accessing other containers within the same network.
Recommended
Patch 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)
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Affected products
2Patches
Vulnerability mechanics
Root cause
"URL validation is performed only on the initial URL, but Playwright automatically follows HTTP redirects without re-validating the destination."
Attack vector
An attacker hosts a benign-looking URL that returns an HTTP 301/302 redirect to an internal address (e.g., `127.0.0.1`, `169.254.169.254`, or a Docker service name like `http://open-webui:8080/api/version`) [ref_id=1]. The attacker provides this URL to Open WebUI's "Web Search" or "URL Loader" feature. The `_safe_process_url_sync` function validates the initial URL and passes it, but Playwright's default behavior follows the redirect without re-validating the destination [ref_id=2]. This bypasses the SSRF protection even when `ENABLE_RAG_LOCAL_WEB_FETCH=False` [ref_id=1].
Affected code
The vulnerability resides in the `SafePlaywrightURLLoader` class. The `_safe_process_url_sync(url)` validation is called only on the initial user-provided URL, and then `page.goto(url)` is invoked without any redirect-chain validation [ref_id=1]. The advisory shows the vulnerable code pattern in the `lazy_load` (and presumably `alazy_load`) methods [ref_id=2].
What the fix does
The recommended patch adds a Playwright route interceptor via `page.route("**/*", intercept_route)` that validates every request URL (including redirect targets) before allowing the connection [ref_id=1]. Inside the interceptor, `validate_url` is called on `route.request.url`; if validation fails, the route is aborted instead of continued [ref_id=2]. This ensures the entire request chain is checked, not just the initial URL. The advisory states this logic should be applied to both `lazy_load` and `alazy_load` methods [ref_id=1].
Preconditions
- configRAG_WEB_LOADER_ENGINE must be set to 'playwright'
- networkAttacker controls a server that returns an HTTP redirect (301/302) to an internal address
- inputUser must input the attacker's URL into the Web Search or URL Loader feature
Reproduction
1. Set environment variables: `ENABLE_RAG_LOCAL_WEB_FETCH=False` and `RAG_WEB_LOADER_ENGINE=playwright`. 2. Run an attack server (e.g., Flask) that returns a 302 redirect to an internal address such as `http://open-webui:8080/api/version` [ref_id=1]. 3. In Open WebUI, use the "Web Search" or "URL Loader" feature and input the attacker's URL (e.g., `http://attacker-ip/`). 4. The Playwright browser follows the redirect and accesses the internal resource [ref_id=2].
Generated on Jun 17, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.