refactor: escape --host in spark serve to prevent CLI command injection#10156
refactor: escape --host in spark serve to prevent CLI command injection#10156sgInnora wants to merge 2 commits intocodeigniter4:developfrom
--host in spark serve to prevent CLI command injection#10156Conversation
The `spark serve` command in system/Commands/Server/Serve.php passes $host (--host CLI option) directly into passthru() without escaping, while sibling arguments ($php, $docroot, $rewrite) on the same line are correctly escaped via escapeshellarg(). Same root pattern as recently-fixed CVE-2025-54418 (ImageMagickHandler _resize/_text command injection): user-controlled string concatenation into a shell command. Although impact is limited here (requires local CLI control over argv to influence --host), the inconsistency makes defense-in-depth and consistency with the surrounding code beneficial. PoC (illustrative, requires local CLI access): php spark serve --host='$(id > /tmp/pwn)' The $(...) substitution executes when passthru() invokes /bin/sh -c. Fix wraps $host with escapeshellarg(), identical to how $php / $docroot / $rewrite are handled on the same passthru() line. Backwards compatibility: escapeshellarg('localhost') returns 'localhost' (single-quoted), which remains a valid 'php -S host:port' argument. Existing functionality unaffected.
|
Hi there, sgInnora! 👋 Thank you for sending this PR! We expect the following in all Pull Requests (PRs).
Important We expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite to prove the code works. If pull requests do not comply with the above, they will likely be closed. Since we are a team of volunteers, we don't have any more time to work See https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md Sincerely, the mergeable bot 🤖 |
--host in spark serve to prevent CLI command injection--host in spark serve to prevent CLI command injection
michalsn
left a comment
There was a problem hiding this comment.
Thanks, good find.
$host is also used for display, so users would see:
http://'localhost':8080
Maybe we can do something like this:
$host = CLI::getOption('host') ?? 'localhost';
$address = escapeshellarg($host . ':' . $port);And then use $address instead of $host and $port in the passthru() call?
…rveTest Address review feedback from @michalsn on PR codeigniter4#10156: codeigniter4#10156 (review) Previously $host was escaped on its own, which made the development server banner display: http://'localhost':8080 Build a single shell-escaped $address = escapeshellarg($host . ':' . $port) for the passthru() call instead, and keep $host in its raw form for the display line. The shell command is still safe against the original `spark serve --host='$(id)'` style injection, while the user-facing output is no longer mangled by stray quotes. Also add tests/system/Commands/Server/ServeTest.php with three cases: - testServeRunsWithDefaultHostAndPort sanity check that the default host/port path is wrapped in single quotes inside the constructed command. - testServeEscapesShellMetacharactersInHost passes --host='$(id)' and asserts the literal substitution token stays inside escapeshellarg's single-quoted wrapper. - testServeEscapesEmbeddedSingleQuoteInHost passes --host="evil'host" and asserts the embedded single quote is escaped via the standard '\\'' sequence rather than breaking out of the argument. The tests intercept passthru() through a function declaration in the CodeIgniter\\Commands\\Server namespace so the real PHP development server is never spawned during test runs.
| { | ||
| $_SERVER['argv'] = ['spark', 'serve', '--host', '$(id)']; | ||
|
|
||
| command('serve --host="$(id)"'); |
There was a problem hiding this comment.
develop cannot parse yet options with =. it's a feature in 4.8.
|
|
||
| public function testServeEscapesShellMetacharactersInHost(): void | ||
| { | ||
| $_SERVER['argv'] = ['spark', 'serve', '--host', '$(id)']; |
There was a problem hiding this comment.
why do you need this separate argv manipulation?
Description
The
php spark servecommand insystem/Commands/Server/Serve.phppasses the--hostCLI option directly intopassthru()without escaping, while sibling arguments ($php,$docroot,$rewrite) on the same line are correctly escaped viaescapeshellarg(). This PR closes the inconsistency.Motivation
Same root pattern as the recently-fixed CVE-2025-54418 (ImageMagickHandler
_resize/_textcommand injection): user-controlled string concatenated into a shell command. Although the impact is limited (requires local CLI control over argv to influence--host), defense-in-depth and consistency with the surrounding code warrant the fix.Vulnerable code (current
develop)PoC (illustrative, requires local CLI access)
$ php spark serve --host='\$(id > /tmp/pwn)' $ cat /tmp/pwn uid=1000(...) gid=1000(...) groups=1000(...)The `$(...)` substitution executes when
passthru()invokes `/bin/sh -c`.Fix
Wrap `$host` with `escapeshellarg()`, identical to how `$php` / `$docroot` / `$rewrite` are handled on the same `passthru()` line:
```diff
```
Backwards compatibility
`escapeshellarg('localhost')` returns `'localhost'` (single-quoted), which remains a valid `php -S host:port` argument. Existing functionality is unaffected.
Display message
Line ~110 prints `'http://...:port'` to stdout via `CLI::write`. After the fix, `$host` is now `'localhost'` (with embedded single quotes). I opted to keep the diff minimal (1 line). Happy to extend with a separate `$rawHost` for cleaner display if maintainers prefer.
Impact assessment
Related