Encode terminal ws session_id to block upstream user_id query injection (#26042)

ws_terminal() interpolated the path parameter session_id directly into the upstream
terminal WebSocket URL and then appended ?user_id=<caller>, with no encoding or
validation (the HTTP sibling proxy_terminal runs _sanitize_proxy_path; this path ran
nothing). An encoded '?'/'&' smuggled through session_id survives Open WebUI's single
decode and is re-decoded by the upstream, injecting an attacker-chosen user_id ahead
of the appended one. Query parsing binds the first occurrence, so the orchestrator
resolves the spoofed user's terminal scope, letting a normal authenticated user
present another user's identity to the upstream (CWE-116/863).

Encode session_id as an opaque path segment with urllib.parse.quote(session_id,
safe=''). This neutralises '?'/'#'/'&' at any decode depth (the upstream's single
decode reverses only the quote, leaving the original delimiters inert as path
content), while legitimate UUID session ids pass through unchanged. The appended
user_id is then the only query parameter the upstream binds.

The separate concern that the forwarded identity is a bearer claim with no integrity
binding spans Open WebUI and the upstream terminal server and is not addressed here.

Co-authored-by: rexpository <30176934+rexpository@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Classic298
2026-06-17 00:15:39 +02:00
committed by GitHub
co-authored by rexpository Claude Opus 4.8
parent 8b35ce924b
commit 5f3a628a8d
+6 -2
View File
@@ -285,10 +285,14 @@ async def ws_terminal(
import urllib.parse
# Encode session_id as an opaque path segment so it cannot smuggle '?'/'#'/'&' (at any
# decode depth) and inject an attacker-chosen user_id ahead of the one appended below.
safe_session_id = urllib.parse.quote(session_id, safe='')
if policy_id:
upstream_url = f'{ws_base}/p/{policy_id}/api/terminals/{session_id}'
upstream_url = f'{ws_base}/p/{policy_id}/api/terminals/{safe_session_id}'
else:
upstream_url = f'{ws_base}/api/terminals/{session_id}'
upstream_url = f'{ws_base}/api/terminals/{safe_session_id}'
if upstream_params:
upstream_url += f'?{urllib.parse.urlencode(upstream_params)}'