baserCMS lets an administrator run the core updater by supplying the path to the PHP binary. The update_core action passes that path straight into a shell command executed by exec() without confirming it is really a PHP interpreter. escapeshellarg() neutralizes shell metacharacters but not the choice of executable, so the value can name any binary on disk. The same parameter is validated elsewhere in the codebase but not on this path, which is the core of the bug.
Summary
| Field |
Value |
| Advisory |
GHSA-vq8m-9cq4-5qh2 |
| Severity |
High (CVSS 3.0: 7.2) |
| CWE |
CWE-78 (OS Command Injection) |
| Package |
baserproject/basercms (Composer) |
| Affected |
<=5.2.2 |
| Vulnerability |
Arbitrary binary execution |
| Impact |
Authenticated remote code execution when chained with a file-write primitive |
How it works
The entry point is PluginsController::update_core(), around lines 150 to 165 of PluginsController.php. It accepts PUT or POST, reads php from the request body, and hands it to the service layer with no checks:
$service->updateCore(
$request->getData('php') ?? 'php', // no validation
$request->getData('connection') ?? 'default'
);
If the update throws, the catch block calls rollbackCore() with the exact same unvalidated php value, so both the update and rollback paths trust attacker input.
Inside PluginsService::updateCore(), around lines 330 to 332 of PluginsService.php, the value becomes the first token of a shell command:
$command = escapeshellarg($php)
. ' ' . escapeshellarg(ROOT . DS . 'bin/cake.php')
. ' update --connection '
. escapeshellarg($connection)
. ' 2>&1';
exec($command, $out, $code);
escapeshellarg() quotes the value so it cannot break out of its argument and inject extra commands. That defense is real but narrow. It guarantees the value is treated as a single argument, not that the argument is a PHP interpreter. Whatever path the attacker supplies is the program exec() runs, with bin/cake.php and the connection flag passed to it as arguments. Point it at any executable on the filesystem and the server runs that executable.
The revealing detail is that baserCMS already knows this input is dangerous. getCoreUpdate(), around lines 825 to 827 of the same service, validates the identical parameter with a strict allowlist regex:
if (!preg_match('/^[a-zA-Z0-9\/\.\-_]+$/', $php)) {
throw new BcException(__d('baser_core', 'PHP実行パスが不正です。'));
}
updateCore() and rollbackCore(), reached from the same admin action, skip that guard entirely.
Root Cause
The php path parameter is validated on one code path (getCoreUpdate()) but not on the two paths reached by update_core (updateCore() and rollbackCore()). The missing check is an allowlist on the binary path. escapeshellarg() is present and gives a false sense of safety because it addresses argument quoting, a different problem than restricting which executable runs. The inconsistency between the validated and unvalidated paths is the defect.