Skip to content

Pheditor: Terminal command-allowlist bypass via argument injection leads to RCE — surviving vector after the metacharacter-sanitization fixes

High severity GitHub Reviewed Published Jul 23, 2026 in pheditor/pheditor • Updated Jul 24, 2026

Package

composer pheditor/pheditor (Composer)

Affected versions

<= 2.0.6

Patched versions

2.0.7

Description

Summary

pheditor's terminal feature restricts callers to an allowlist of commands (TERMINAL_COMMANDS) and rejects shell metacharacters. The allowlist is enforced as a PREFIX match with no argument validation, and the allowlist includes binaries that grant arbitrary command execution through their own options (find, git, php, tar, grep). A caller can therefore run any command using only allowlisted binaries and no rejected metacharacter, escaping the allowlist restriction the terminal feature relies on.

Relationship to the prior terminal advisories (this is a surviving, distinct vector)

The prior terminal advisories were all shell-metacharacter injections: GHSA-9643-6xjp-vx57 ($()), GHSA-wg4w-wr5q-6vjc (|, backtick, newline), GHSA-jvc5-58fv-w8cq (; via the dir field). The current code rejects those characters. This report is a different class — CWE-88 argument injection through an allowlisted binary's flags — which the metacharacter denylist does not address.

Root cause (HEAD, v2.0.6)

In the terminal action handler of pheditor.php:

  1. :588 rejects &, ;, |, $, backtick, \n, \r. It does NOT reject space, -, {, }, +, /, ..
  2. :595-605 checks the command against TERMINAL_COMMANDS (defined :25: ls,...,php,...,git,find,grep,...,tar,...,composer,exit) using a PREFIX match: strlen($command) >= strlen($value) && substr($command, 0, strlen($value)) == $value. There is no word boundary and no validation of the arguments that follow.
  3. :617 runs the command through the shell unchanged: shell_exec((empty($dir) ? null : 'cd ' . escapeshellarg($dir) . ' && ') . $command . ' && echo \ ; pwd').

So a command beginning with an allowlisted binary, carrying a code-exec flag, and containing none of the rejected characters reaches shell_exec intact.

Proof of concept (reproduced)

POST to the terminal action with:
command = find . -maxdepth 0 -exec touch /tmp/PWNED {} +
dir = (any)

This contains no rejected metacharacter, prefix-matches the allowlisted find, and find -exec runs an arbitrary program. A faithful harness mirroring the three guards (poc/reproduction.sh, poc/transcript.txt) creates the marker file. Other allowlisted-binary payloads with the same property: git -c alias.x='!touch /tmp/PWNED' x, php -r 'system("id");', tar -cf /dev/null --checkpoint=1 --checkpoint-action=exec="touch /tmp/PWNED" ..

Impact

Arbitrary command execution on the host, under the web server's privileges, for a caller with the terminal permission (enabled in the default configuration). The exposure is amplified by GHSA-p4h7-p9rj-2pq2 (hardcoded default admin password with no forced change): a default deployment grants the authenticated access needed to reach the terminal action with a single known credential, making the chain effectively unauthenticated RCE.

Remediation

Validate the FULL command, not just its prefix: tokenize and require the program to be an allowlisted binary AND constrain its arguments (reject -exec/-execdir for find, -c/--upload-pack for git, -r/-d for php, --checkpoint-action/--to-command for tar, -f/--file program forms, etc.), or run each command as an argv array through a restricted launcher with no shell, or remove the code-exec-capable binaries from the allowlist. A prefix allowlist over a shell sink cannot constrain capability.

Credit: anir0y (independent security research).

References

@hamidsamak hamidsamak published to pheditor/pheditor Jul 23, 2026
Published to the GitHub Advisory Database Jul 24, 2026
Reviewed Jul 24, 2026
Last updated Jul 24, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

EPSS score

Weaknesses

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component. Learn more on MITRE.

Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')

The product constructs a string for a command to be executed by a separate component in another control sphere, but it does not properly delimit the intended arguments, options, or switches within that command string. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-g3hq-hphg-8fhh

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.