[Eclipse Theia] Cross-Origin WebSocket Access To Shell-Terminal Enables Command Execution And Output Exfiltration
From the security Mailing List:
# Cross-Origin WebSocket Access To Shell-Terminal Enables Command Execution And Output Exfiltration
## Executive Summary
The browser backend exposes privileged terminal RPC over the shared Socket.IO namespace (`/services`) without a terminal-specific authentication/authorization gate. A foreign-origin webpage can open the `/services/shell-terminal` channel, invoke terminal creation, connect to `/services/terminals/<id>`, execute commands, and read command output.
I verified this non-destructively by executing `id` and extracting `uid=...` output from the terminal stream in a real browser.
On the tested checkout, this worked in both:
- default mode (without `THEIA_HOSTS`), and
- the script's `THEIA_HOSTS=<host:port>` phase.
## Product / Asset
- Product: Eclipse Theia browser backend with `@theia/terminal`.
- Transport: Socket.IO namespace `/services`.
- Privileged channel: `/services/shell-terminal`.
- Terminal data channel: `/services/terminals/:id`.
## Suggested Severity
- Suggested severity: High (potentially Critical depending on deployment exposure).
- CWE: CWE-306 (Missing Authentication for Critical Function), CWE-1385 (Missing Origin Validation in WebSockets).
- CVSS v3.1 (drive-by browser case): `AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H`.
## Why This Matters
- Local developer setup risk: a user running Theia locally can be compromised by visiting a malicious webpage.
- Public deployment risk: if the backend is exposed and upstream auth is weak/misconfigured, this becomes direct remote command execution.
## High-Confidence Evidence
- Reproduction uses a real browser from a foreign origin.
- No Node-only headers are required by the PoC page.
- PoC executes `id` and extracts actual terminal output (`uid=...`) end-to-end.
- Reproduction is non-destructive (no file write or persistence payload required).
## Affected Code Paths
- `packages/terminal/src/node/terminal-backend-module.ts`
- Registers `/services/shell-terminal` RPC handler with no authz gate.
- `packages/terminal/src/node/shell-terminal-server.ts`
- `create(options)` accepts remote-controlled options and spawns shell process.
- `packages/terminal/src/node/shell-process.ts`
- Terminal options flow into command/args/cwd/env.
- `packages/process/src/node/terminal-process.ts`
- Executes sink via `node-pty.spawn(...)`.
- `packages/core/src/node/messaging/default-messaging-service.ts`
- Shared `/services` multiplex transport.
- `packages/core/src/common/message-rpc/channel.ts`
- Logical channel open protocol (`Open`, `AckOpen`, `Data`).
- `packages/core/src/node/hosting/ws-origin-validator.ts`
- Fail-open when `Origin` is missing.
- `packages/core/src/node/messaging/websocket-endpoint.ts`
- Overwrites `request.headers.origin` with optional `fix-origin` header before validation.
- `packages/core/README.md`
- Documents `THEIA_HOSTS` as WebSocket origin control and states any-origin behavior when unset.
## Reproduction (Maintainer Quick Path)
### Tested Environment
- Repository: `eclipse-theia/theia`
- Tested commit: `6e4fccbd9f69886b679e3d34ef27218e87885659`
- Date: 2026-03-11
### One-Command Repro
From repository workspace root (the directory containing `safe_full_app_cross_origin_demo.sh`):
bash
THEIA_PORT=51423 ATTACK_PORT=51424 DEMO_INTERACTIVE=0 KEEP_RUNNING=0 bash safe_full_app_cross_origin_demo.sh
### What The Script Does
1. Starts Theia browser example on `THEIA_PORT`.
2. Serves attacker page on different origin (`ATTACK_PORT`).
3. Foreign-origin page connects to `/services`, performs `initialConnection`, opens `/services/shell-terminal`, invokes `create`, opens `/services/terminals/<id>`, sends `id`, and reads output.
4. Repeats in a second phase with `THEIA_HOSTS=<THEIA_HOST>:<THEIA_PORT>` set by the script.
### Success Signals
The attacker page log includes:
- `ack-open`
- `create-request:1`
- `reply:<terminalId>`
- `terminal-open-ok`
- `id-dispatched`
- `id-output:uid=...`
- `id-output-ok`
Script-level success requires `id-output-ok` in both phases.
## Expected vs Actual
### Expected
- Untrusted foreign-origin clients cannot reach privileged terminal RPC.
- With host restrictions enabled, cross-origin websocket attempts should be denied in browser deployments.
- Terminal creation should require authenticated/authorized principal.
### Actual
- Foreign-origin page executes `id` and exfiltrates output through terminal channel.
- On tested checkout, exploit succeeds in both default and `THEIA_HOSTS` phase.
## Root Cause Analysis
1. Privileged RPC (`/services/shell-terminal`) assumes transport reachability equals trust.
2. `create()` allows caller-controlled process options into PTY spawn path.
3. WebSocket origin admission is optional/fail-open in some paths (missing `Origin` accepted).
4. The endpoint rewrites origin from optional `fix-origin`, which weakens guarantees if absent/unreliable.
## Impact Scenarios
- Drive-by local compromise: attacker page triggers command execution as victim user while Theia runs locally.
- Hosted/tunneled misconfiguration: direct remote command execution if backend websocket is reachable without strong external auth.
- Post-exploitation capability: read/write repository files, access secrets in environment, alter build outputs.
## Recommended Remediation
1. Enforce authn/authz at terminal RPC service boundary (do not trust network reachability alone).
2. Make websocket admission fail-closed for browser target:
- reject missing/invalid origin,
- avoid replacing trusted origin context with user-supplied fallback headers,
- require strict host allowlist matching.
3. Bind terminal creation/attach to authenticated session principals and explicit permissions.
4. Add defense-in-depth runtime controls (disable terminal service when not needed, least privilege process environment).
[exploit.sh](/uploads/f08e40a8573acc3cfcc31950f528930f/exploit.sh)
issue