Skip to content

Hardening: Path traversal in package install via composer.json name #236

@swissspidy

Description

@swissspidy

A security report highlighted a weakness in the wp package install <URL> command where a malicious ZIP package can achieve persistent Remote Code Execution (RCE) by exploiting a path traversal vulnerability.

While WP-CLI commands are highly privileged and installing untrusted packages is inherently risky, this specific issue violates the expectation that a failed installation should not leave persistent side effects. In this case, even when Composer rejects the package name and the install fails, a malicious file is already placed outside the intended sandbox.

Technical Details

  1. Path Traversal: The flaw lies in src/Package_Command.php. When installing a package from a URL or local ZIP, WP-CLI reads the composer.json from the extracted files. If the name field is set to .., the path resolved for moving the files can escape the intended directory.
  2. Autoloader Overwrite: An attacker can place a malicious vendor/autoload.php file in the ZIP. Due to the path traversal, this file lands at ~/.wp-cli/packages/vendor/autoload.php, which is the aggregate autoloader that WP-CLI loads on every subsequent invocation (unless --skip-packages is used).
  3. Missing Containment Check: The framework function Extractor::copy_overwrite_files() in php/WP_CLI/Extractor.php lacks a base-path containment check. It copies files without verifying that the resolved destination stays under the target directory. A sibling function, Extractor::rmdir(), already implements such a check.
  4. Race Condition/Timing: The copy operation happens before Composer validates the composer.json file. Therefore, even though Composer eventually fails the installation because .. is not a valid package name, the payload is already on disk.

Proposed Hardening

  1. Validate Package Name: In src/Package_Command.php, validate the package name read from composer.json against Composer's canonical regex before performing any file operations. Reject invalid names immediately.
  2. Add Containment Checks: Harden Extractor::copy_overwrite_files() by implementing a base-path containment check using realpath(), mirroring the logic already present in Extractor::rmdir().

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions