# Open WebUI: Path traversal / SSRF in terminal server proxy via encoded path traversal

Open WebUI terminal proxy forwarded user-controlled path without full decoding, letting authenticated users escape path/policy scope via double-encoded %252e%252e traversal and reach internal endpoints.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: Tulgaaaaaaaa, sermikr0
- Disclosed: 2026-06-17
- CVE: CVE-2026-54017
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r2wg-2mcr-66rv
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-r2wg-2mcr-66rv
- Tags: area:web domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:python technique:incomplete-fix vuln:path-traversal vuln:ssrf

**Target:** Terminal-server reverse proxy route /api/v1/terminals/{server_id}/{path}
**Root cause:** Proxy concatenated user-controlled path onto terminal-server base URL; the added _sanitize_proxy_path decoded only once before normpath/.. check, so double-encoded %252e%252e survived and was re-decoded upstream into ../ traversal.
**Steps:**
1. Authenticate as a non-admin user granted access to a terminal server (server1).
2. Send `GET /api/v1/terminals/server1/%252e%252e/%252e%252e/sensitive-file`.
3. Sanitizer unquotes once to `%2e%2e/%2e%2e/sensitive-file`, normpath leaves it, no leading `..`, passes check.
4. Proxy forwards `{base_url}/%2e%2e/%2e%2e/sensitive-file`; upstream decodes to `../../sensitive-file` and resolves traversal.
5. With policy_id set, target becomes `{base_url}/p/{policy_id}/%2e%2e/...`, escaping the policy namespace.
**How found:** Original reporter noticed raw path concatenation allowed single-encoded `..%2F` traversal; after the single-decode sanitizer was added, the second reporter reasoned that one unquote pass leaves `%2e%2e` intact and tested double-encoding to bypass the `..` check.
**Exploitation:** Any authenticated user with terminal-server access issues crafted requests through the proxy route; the double-encoded traversal escapes the intended path or policy scope on the terminal-server host, reaching unintended files/endpoints, and where the terminal server fans out to internal services, provides SSRF-style reach.
**Escalation:** Using the policy_id form ({base_url}/p/{policy_id}/{safe_path}) traversal escapes the policy namespace to reach other policies or the terminal-server root, and onward internal services.
**Insight:** When a sanitizer decodes once but the downstream server decodes again, encode the traversal one level deeper than the check; always test mitigations with double/triple-encoded payloads and check whether the upstream re-decodes.

## Proof of concept (copied exactly from the original report)

**Double-encoded traversal bypass request**

```
GET /api/v1/terminals/server1/%252e%252e/%252e%252e/sensitive-file
# passes _sanitize_proxy_path as %2e%2e/%2e%2e/sensitive-file
# upstream decodes -> ../../sensitive-file
```

**Single-encoded traversal request**

```
GET /api/v1/terminals/server1/..%2F..%2F..%2Finternal-api/secrets
# proxied to: {base_url}/../../../internal-api/secrets
```

