Encode terminal ws session_id to block upstream user_id query injection

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-14 12:28:08 +02:00
parent f85cb27ef8
commit 1a94efcc21

View file

@ -282,10 +282,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)}'