CVE-2019-10774
Description
Command injection in php-shellcommand before 1.6.1 allows arbitrary code execution via unsanitized argument strings.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Command injection in php-shellcommand before 1.6.1 allows arbitrary code execution via unsanitized argument strings.
Vulnerability
CVE-2019-10774 is a command injection vulnerability in the php-shellcommand library, affecting versions prior to 1.6.1. The root cause is that the addArg() method concatenates user-supplied input directly into the command string without proper escaping, allowing shell metacharacters to be interpreted. This bypasses the library's intended argument escaping mechanism and can lead to arbitrary code execution [1][2][4].
Exploitation
To exploit this vulnerability, an attacker must be able to supply malicious input to the addArg() function, typically through user-controlled data passed to the application. The library's earlier versions did not correctly handle cases where arguments contained shell metacharacters such as || or quotes, which could be used to inject additional commands. Notably, the fix introduced in version 1.6.1 enclosed all arguments in single quotes to prevent such injection [3].
Impact
Successful exploitation allows an attacker to execute arbitrary shell commands on the server with the privileges of the web application process. This can lead to full system compromise, data exfiltration, or lateral movement within the network. The vulnerability is classified as critical with a CVSS score of 9.8 (Critical) [2][4].
Mitigation
The vulnerability is fixed in version 1.6.1 of php-shellcommand. Users should upgrade immediately via Composer. Patches were released on December 20, 2019. There is no mention of a workaround; upgrading is the recommended action [3][4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
mikehaertl/php-shellcommandPackagist | < 1.6.1 | 1.6.1 |
Affected products
1Patches
18d98d8536e05Merge pull request #45 from Kirill89/master
3 files changed · +34 −12
src/Command.php+13 −6 modified@@ -272,7 +272,7 @@ public function getArgs() * @param string|array|null $value the optional argument value which will * get escaped if $escapeArgs is true. An array can be passed to add more * than one value for a key, e.g. `addArg('--exclude', - * array('val1','val2'))` which will create the option `--exclude 'val1' + * array('val1','val2'))` which will create the option `'--exclude' 'val1' * 'val2'`. * @param bool|null $escape if set, this overrides the $escapeArgs setting * and enforces escaping/no escaping @@ -288,18 +288,25 @@ public function addArg($key, $value = null, $escape = null) setlocale(LC_CTYPE, $this->locale); } if ($value === null) { - // Only escape single arguments if explicitely requested - $this->_args[] = $escape ? escapeshellarg($key) : $key; + $this->_args[] = $doEscape ? escapeshellarg($key) : $key; } else { - $separator = substr($key, -1)==='=' ? '' : ' '; + if (substr($key, -1) === '=') { + $separator = '='; + $argKey = substr($key, 0, -1); + } else { + $separator = ' '; + $argKey = $key; + } + $argKey = $doEscape ? escapeshellarg($argKey) : $argKey; + if (is_array($value)) { $params = array(); foreach ($value as $v) { $params[] = $doEscape ? escapeshellarg($v) : $v; } - $this->_args[] = $key . $separator.implode(' ',$params); + $this->_args[] = $argKey . $separator . implode(' ', $params); } else { - $this->_args[] = $key . $separator . + $this->_args[] = $argKey . $separator . ($doEscape ? escapeshellarg($value) : $value); } }
tests/BlockingCommandTest.php+1 −1 modified@@ -62,7 +62,7 @@ public function testCanRunCommandWithArguments() $command->nonBlockingMode = false; $command->addArg('-l'); $command->addArg('-n'); - $this->assertEquals("ls -l -n", $command->getExecCommand()); + $this->assertEquals("ls '-l' '-n'", $command->getExecCommand()); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted());
tests/CommandTest.php+20 −5 modified@@ -81,8 +81,8 @@ public function testCanAddArguments() $command->addArg('-b=', array('v4','v5','v6')); $command->addArg('-c', ''); $command->addArg('some name', null, true); - $this->assertEquals("--arg1=x --a --a '中文字äüp' --a 'v'\''1' 'v2' 'v3' -b=v -b='v4' 'v5' 'v6' -c '' 'some name'", $command->getArgs()); - $this->assertEquals("test --arg1=x --a --a '中文字äüp' --a 'v'\''1' 'v2' 'v3' -b=v -b='v4' 'v5' 'v6' -c '' 'some name'", $command->getExecCommand()); + $this->assertEquals("--arg1=x '--a' '--a' '中文字äüp' '--a' 'v'\''1' 'v2' 'v3' -b=v '-b'='v4' 'v5' 'v6' '-c' '' 'some name'", $command->getArgs()); + $this->assertEquals("test --arg1=x '--a' '--a' '中文字äüp' '--a' 'v'\''1' 'v2' 'v3' -b=v '-b'='v4' 'v5' 'v6' '-c' '' 'some name'", $command->getExecCommand()); } public function testCanResetArguments() { @@ -102,14 +102,29 @@ public function testCanDisableEscaping() $command->addArg('-b=','v', true); $command->addArg('-b=', array('v4','v5','v6')); $command->addArg('some name', null, true); - $this->assertEquals("--a --a v --a v1 v2 v3 -b='v' -b=v4 v5 v6 'some name'", $command->getArgs()); + $this->assertEquals("--a --a v --a v1 v2 v3 '-b'='v' -b=v4 v5 v6 'some name'", $command->getArgs()); + } + public function testCanPreventCommandInjection() + { + $command = new Command(array( + 'command' => 'curl', + )); + $command->addArg('http://example.com --wrong-argument || echo "RCE 1"'); + $this->assertEquals("'http://example.com --wrong-argument || echo \"RCE 1\"'", $command->getArgs()); + + $command = new Command(array( + 'command' => 'curl', + )); + $command->addArg('http://example.com'); + $command->addArg('--header foo --wrong-argument || echo "RCE 2" ||', 'bar'); + $this->assertEquals("'http://example.com' '--header foo --wrong-argument || echo \"RCE 2\" ||' 'bar'", $command->getArgs()); } public function testCanRunCommandWithArguments() { $command = new Command('ls'); $command->addArg('-l'); $command->addArg('-n'); - $this->assertEquals("ls -l -n", $command->getExecCommand()); + $this->assertEquals("ls '-l' '-n'", $command->getExecCommand()); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); @@ -163,7 +178,7 @@ public function testCanCastToString() $command = new Command('ls'); $command->addArg('-l'); $command->addArg('-n'); - $this->assertEquals("ls -l -n", (string)$command); + $this->assertEquals("ls '-l' '-n'", (string)$command); } // Exec
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.