GHSA-r2wg-2mcr-66rv Open WebUI: Path traversal / SSRF in terminal server proxy via encoded path traversal
SummaryThe terminal-server reverse proxy in `backend/open_webui/routers/terminals.py` does not fully confine the user-controlled `path` segment before forwarding it to an admin-configured terminal server. An authenticated user who has been granted access to a terminal server can craft `path` values containing encoded `../` traversal sequences that escape the intended path (or policy) scope on that server, reaching unintended endpoints and files on the terminal-server host. Where the terminal server fans requests out to internal services, this also gives SSRF-style reach into those services.This is a separate code path from the `/api/v1/retrieval/process/web` SSRF (GHSA-c6xv-rcvw-v685), with its own input. Two distinct vectors are consolidated here:Raw path forwarding / single-encoded traversal (original report).A bypass of the subsequently-added `_sanitize_proxy_path` mitigation using double-encoded dots (`%252e%252e`).The attacker-controlled input is the request `path`, supplied by the non-admin user, not anything an administrator configures, so this is not an admin-trust / Rule-9 situation.Affected codeThe proxy route forwards an arbitrary trailing path to the configured terminal server:Access requires `has_connection_access(user, connection, ...)`, i.e. a non-admin user the administrator has granted to that terminal server.Vector 1 — single-encoded traversal (original)The path was originally concatenated to the base URL with no sanitization (`target_url = f"{base_url}/{path}"`), so single-encoded traversal escaped the intended scope:This vector is closed at HEAD: `_sanitize_proxy_path` now URL-decodes once, runs `posixpath.normpath`, strips leading slashes, and rejects results beginning with `..` (`unquote('..%2F..%2F') -> '../../' -> normpath -> '../..'` -> rejected).Vector 2 — double-encoded bypass of `_sanitize_proxy_path``_sanitize_proxy_path` decodes the path only once before the `..` check, so a double-encoded payload survives:`unquote('%252e%252e/secret')` yields `%2e%2e/secret` (not `..`), which `normpath` leaves unchanged and which does not start with `..`, so it passes the check. The proxy then forwards `{base_url}/%2e%2e/secret`, and the upstream terminal server decodes `%2e%2e` into `..` and resolves the traversal the check was meant to prevent.The `policy_id` form (`{base_url}/p/{policy_id}/{safe_path}`) is the higher-impact target: traversal escapes the policy namespace and reaches other policies or the terminal-server root.ImpactAn authenticated user with access to a terminal server can escape the intended path/policy scope on that server, reaching unintended endpoints and files, and, where the terminal server routes onward to internal services, reach those services. CWE-22 (Path Traversal) and CWE-918 (SSRF).FixDecode the proxy path until it is stable before normalising and checking, so no depth of encoding can smuggle a traversal sequence past the check to be re-decoded upstream:This rejects `%2e%2e`, `%252e%252e`, `%25252e%25252e`, `..%2f..%2f`, etc., while leaving legitimate paths (including singly-encoded characters such as `%20`) intact.Credits**Tulgaaaaaaaa** — original report (terminal-proxy path SSRF / single-encoded traversal).**sermikr0** — double-encoded (`%252e%252e`) bypass of the `_sanitize_proxy_path` mitigation.
CVSS v3.1 score: 7.7