Skip to content

Commit 2bab79f

Browse files
committed
Update addArg() to improve escaping (fix #44)
1 parent 6c6f44c commit 2bab79f

File tree

3 files changed

+34
-16
lines changed

3 files changed

+34
-16
lines changed

src/Command.php

+13-10
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,8 @@ public function getArgs()
272272
* @param string|array|null $value the optional argument value which will
273273
* get escaped if $escapeArgs is true. An array can be passed to add more
274274
* than one value for a key, e.g. `addArg('--exclude',
275-
* array('val1','val2'))` which will create the option `--exclude 'val1'
276-
* 'val2'`.
275+
* array('val1','val2'))` which will create the option `'--exclude' 'val1'
276+
* '--exclude' 'val2'`.
277277
* @param bool|null $escape if set, this overrides the $escapeArgs setting
278278
* and enforces escaping/no escaping
279279
* @return static for method chaining
@@ -288,19 +288,22 @@ public function addArg($key, $value = null, $escape = null)
288288
setlocale(LC_CTYPE, $this->locale);
289289
}
290290
if ($value === null) {
291-
// Only escape single arguments if explicitely requested
292-
$this->_args[] = $escape ? escapeshellarg($key) : $key;
291+
$this->_args[] = $doEscape ? escapeshellarg($key) : $key;
293292
} else {
294-
$separator = substr($key, -1)==='=' ? '' : ' ';
295293
if (is_array($value)) {
296-
$params = array();
297294
foreach ($value as $v) {
298-
$params[] = $doEscape ? escapeshellarg($v) : $v;
295+
$this->addArg($key, $v, $escape);
299296
}
300-
$this->_args[] = $key . $separator.implode(' ',$params);
301297
} else {
302-
$this->_args[] = $key . $separator .
303-
($doEscape ? escapeshellarg($value) : $value);
298+
if (substr($key, -1) === '=') {
299+
$this->_args[] = $doEscape ?
300+
escapeshellarg($key . $value) :
301+
$key . $value;
302+
} else {
303+
$this->_args[] = $doEscape ?
304+
escapeshellarg($key) . ' ' . escapeshellarg($value) :
305+
$key . ' ' . $value;
306+
}
304307
}
305308
}
306309
if ($useLocale) {

tests/BlockingCommandTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function testCanRunCommandWithArguments()
6262
$command->nonBlockingMode = false;
6363
$command->addArg('-l');
6464
$command->addArg('-n');
65-
$this->assertEquals("ls -l -n", $command->getExecCommand());
65+
$this->assertEquals("ls '-l' '-n'", $command->getExecCommand());
6666
$this->assertFalse($command->getExecuted());
6767
$this->assertTrue($command->execute());
6868
$this->assertTrue($command->getExecuted());

tests/CommandTest.php

+20-5
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public function testCanAddArguments()
8181
$command->addArg('-b=', array('v4','v5','v6'));
8282
$command->addArg('-c', '');
8383
$command->addArg('some name', null, true);
84-
$this->assertEquals("--arg1=x --a --a '中文字äüp' --a 'v'\''1' 'v2' 'v3' -b=v -b='v4' 'v5' 'v6' -c '' 'some name'", $command->getArgs());
85-
$this->assertEquals("test --arg1=x --a --a '中文字äüp' --a 'v'\''1' 'v2' 'v3' -b=v -b='v4' 'v5' 'v6' -c '' 'some name'", $command->getExecCommand());
84+
$this->assertEquals("--arg1=x '--a' '--a' '中文字äüp' '--a' 'v'\''1' '--a' 'v2' '--a' 'v3' -b=v '-b=v4' '-b=v5' '-b=v6' '-c' '' 'some name'", $command->getArgs());
85+
$this->assertEquals("test --arg1=x '--a' '--a' '中文字äüp' '--a' 'v'\''1' '--a' 'v2' '--a' 'v3' -b=v '-b=v4' '-b=v5' '-b=v6' '-c' '' 'some name'", $command->getExecCommand());
8686
}
8787
public function testCanResetArguments()
8888
{
@@ -102,14 +102,29 @@ public function testCanDisableEscaping()
102102
$command->addArg('-b=','v', true);
103103
$command->addArg('-b=', array('v4','v5','v6'));
104104
$command->addArg('some name', null, true);
105-
$this->assertEquals("--a --a v --a v1 v2 v3 -b='v' -b=v4 v5 v6 'some name'", $command->getArgs());
105+
$this->assertEquals("--a --a v --a v1 --a v2 --a v3 '-b=v' -b=v4 -b=v5 -b=v6 'some name'", $command->getArgs());
106+
}
107+
public function testCanPreventCommandInjection()
108+
{
109+
$command = new Command(array(
110+
'command' => 'curl',
111+
));
112+
$command->addArg('http://example.com --wrong-argument || echo "RCE 1"');
113+
$this->assertEquals("'http://example.com --wrong-argument || echo \"RCE 1\"'", $command->getArgs());
114+
115+
$command = new Command(array(
116+
'command' => 'curl',
117+
));
118+
$command->addArg('http://example.com');
119+
$command->addArg('--header foo --wrong-argument || echo "RCE 2" ||', 'bar');
120+
$this->assertEquals("'http://example.com' '--header foo --wrong-argument || echo \"RCE 2\" ||' 'bar'", $command->getArgs());
106121
}
107122
public function testCanRunCommandWithArguments()
108123
{
109124
$command = new Command('ls');
110125
$command->addArg('-l');
111126
$command->addArg('-n');
112-
$this->assertEquals("ls -l -n", $command->getExecCommand());
127+
$this->assertEquals("ls '-l' '-n'", $command->getExecCommand());
113128
$this->assertFalse($command->getExecuted());
114129
$this->assertTrue($command->execute());
115130
$this->assertTrue($command->getExecuted());
@@ -163,7 +178,7 @@ public function testCanCastToString()
163178
$command = new Command('ls');
164179
$command->addArg('-l');
165180
$command->addArg('-n');
166-
$this->assertEquals("ls -l -n", (string)$command);
181+
$this->assertEquals("ls '-l' '-n'", (string)$command);
167182
}
168183

169184
// Exec

0 commit comments

Comments
 (0)