Apr 20, 2026·8 min read·3 visits
PHPUnit versions prior to 13.1.6 fail to properly escape INI settings passed to child processes via the command line. Attackers who control configuration values can inject newlines to execute arbitrary files via injected INI directives.
An argument injection vulnerability exists in PHPUnit's JobRunner component due to improper neutralization of metacharacters in PHP INI configuration values. This flaw allows an attacker to inject arbitrary INI directives during process forking, potentially leading to remote code execution within the context of continuous integration environments or testing workers.
PHPUnit utilizes a JobRunner component to manage process isolation and parallel worker execution during testing. Process isolation ensures that individual test cases do not pollute the shared state of the PHP environment. To maintain consistency across these environments, the parent process extracts its currently active PHP INI configuration and forwards it to the forked child process.
The vulnerability manifests in the transmission of these configuration values. The parent process passes the INI settings as command-line arguments to the PHP CLI executable using the -d name=value format. The application constructs this command string without neutralizing special characters embedded within the configuration values.
Because the PHP CLI parser processes the -d argument sequentially and relies on specific metacharacters for structure, unescaped characters disrupt the intended boundaries of the configuration directive. This constitutes an Argument Injection vulnerability (CWE-88), where untrusted data alters the structure of the command execution sequence.
Attackers who possess the ability to manipulate PHPUnit configuration files or relevant environment variables can leverage this weakness. By introducing specific metacharacters into an INI value, the attacker forces the PHP CLI to interpret subsequent text as an entirely distinct configuration directive, expanding their control over the execution environment.
The fundamental flaw resides in the absence of boundary protection when constructing the command line for the isolated worker. The JobRunner iterates over the associative array of INI settings and directly concatenates them into the execution string. It assumes that the values are benign scalars that do not contain command-shell or application-specific control characters.
The PHP CLI INI parser treats the -d flag as a mechanism to override configuration directives. During processing, the parser interprets certain characters as structural indicators. A semicolon (;) designates the beginning of a comment, truncating the remainder of the value. Double quotes (") serve as string delimiters, and improper nesting breaks the syntactic validity of the argument.
Crucially, the parser evaluates a newline character (\n or \r) as the termination of the current configuration directive. If a newline appears within the value segment of a -d argument, the parser concludes the processing of the initial directive and evaluates the text following the newline as a completely new INI key-value pair. The application failed to anticipate this behavior and did not implement a sanitization routine.
The diagram above illustrates the data flow. The lack of a sanitization boundary at the JobRunner construction phase permits the injection payload to reach the PHP CLI parser intact. The parser, operating according to its specification, blindly processes the injected directive, instantiating the security failure.
Prior to the patch, the JobRunner process constructed the command-line arguments using a direct concatenation pattern. The code extracted the configuration values and appended them with a -d prefix, making no distinction between trusted system values and potentially user-controlled inputs. This direct translation from configuration state to execution arguments created the injection vector.
The remediation, implemented in Pull Request #6592, introduces a conditional quoting and escaping mechanism. The patch analyzes each configuration value prior to concatenation. The analysis checks for the presence of semicolons (;) or double quotes (") within the string. These characters indicate that the value requires structural protection to survive the transition to the child process intact.
When the code detects these metacharacters, it applies a specific sanitization routine. First, the value is encapsulated in double quotes. This encapsulation instructs the CLI parser to treat the entire sequence as a single string literal. Second, any internal double quotes are escaped (converted to \") to prevent premature termination of the string boundary.
This remediation ensures that internal newlines or semicolons are evaluated strictly as string data rather than configuration directives. The encapsulation mechanism safely bounds the configuration value, neutralizing the execution context transition issue. The fix specifically addresses the parser's syntax rules, providing a complete structural defense against the injection technique.
Exploiting this vulnerability requires the attacker to exert control over a PHP INI setting evaluated by the parent PHPUnit process. The most common vector for this control is the phpunit.xml configuration file. In many continuous integration pipelines, developers or automated systems pull configuration files directly from a source code repository.
An attacker begins the exploit sequence by submitting a malicious pull request or committing code that alters the phpunit.xml file. The attacker targets an arbitrary INI setting, such as memory_limit or error_reporting, and crafts a payload containing a newline character. Following the newline, the attacker appends a dangerous directive, typically auto_prepend_file.
The payload takes the structure memory_limit="128M\nauto_prepend_file=/tmp/exploit.php". When the continuous integration system initiates the testing suite, the parent PHPUnit process loads this configuration. As the process forks to execute isolated tests, the JobRunner concatenates the payload into the child process command line without sanitization.
<!-- Example Malicious phpunit.xml -->
<phpunit bootstrap="vendor/autoload.php">
<php>
<ini name="memory_limit" value="128M auto_prepend_file=/tmp/exploit.php"/>
</php>
<!-- Test suites omitted -->
</phpunit>The child process executes the resulting command. The PHP CLI parser processes the initial memory_limit directive, encounters the newline, and processes the injected auto_prepend_file directive. Upon initialization, the child process immediately executes the PHP code located at /tmp/exploit.php before executing any test logic. This achieves arbitrary code execution within the testing worker.
The successful exploitation of this argument injection yields high impact across confidentiality, integrity, and availability. The primary consequence is unauthenticated Remote Code Execution (RCE). By abusing the auto_prepend_file or auto_append_file INI directives, the attacker achieves arbitrary command execution within the context of the underlying operating system user running the PHPUnit process.
The severity of this execution context heavily depends on the execution environment. In typical scenarios, PHPUnit runs within automated Continuous Integration and Continuous Deployment (CI/CD) pipelines. Compromise of a CI/CD runner provides the attacker with a highly privileged network position. The attacker can exfiltrate sensitive environment variables, deployment secrets, database credentials, and proprietary source code.
Furthermore, the attacker can leverage the injection vector to disable existing security controls. Injecting the disable_functions directive with an empty value effectively neutralizes PHP-level security restrictions applied to the parent process. This allows the execution of functions like system(), exec(), or shell_exec() that administrators typically restrict in hardened environments.
The vulnerability is assessed with a CVSS severity of 7.8 (High). While the impact is severe, the attack complexity is elevated by the prerequisite condition: the attacker must secure the ability to influence the PHPUnit configuration file or the environment variables defining INI states. In collaborative development environments utilizing pull requests, this prerequisite is frequently attainable.
The definitive resolution for this vulnerability is upgrading the phpunit/phpunit package to version 13.1.6 or later. This release incorporates the necessary structural escaping and quoting mechanisms within the JobRunner component. Organizations utilizing major versions 12 or 11 must verify the availability of backported patches or prioritize migration to a supported release branch.
In environments where immediate upgrading is technically prohibitive, security teams must implement strict validation controls over the phpunit.xml file. Administrators should configure branch protection rules in the source control system to mandate rigorous code review for any modifications to testing configuration files. Pipelines should enforce static analysis to detect newline characters or unexpected INI directives within configuration definitions.
Security engineers can deploy Static Application Security Testing (SAST) tools to proactively identify vulnerable package versions across code repositories. Analyzing dependency lockfiles (composer.lock) provides immediate visibility into the exposure surface. The provided Nuclei template demonstrates a method for automating this discovery process by scanning lockfiles for versions predating the fix.
> [!NOTE] Nuclei Detection Rule
id: GHSA-QRR6-MG7R-M243-Check
info:
name: PHPUnit Argument Injection Version Check
severity: high
description: Checks if the installed version of phpunit/phpunit is vulnerable to INI injection.
file:
- extensions:
- lock
extractors:
- type: regex
name: phpunit-version
regex:
- '"name": "phpunit/phpunit",\s+"version": "([0-9.]+)"'Organizations must pair these detections with strict least-privilege principles within the CI/CD environment. Build runners must execute within isolated, ephemeral containers lacking access to production secrets unless strictly necessary. Limiting the execution context ensures that a successful compromise of the runner does not cascade into a broader infrastructure breach.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
PHPUnit 13 sebastianbergmann | < 13.1.6 | 13.1.6 |
PHPUnit 12 sebastianbergmann | < 12.x | - |
PHPUnit 11 sebastianbergmann | < 11.x | - |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-88 (Argument Injection or Modification) |
| Attack Vector | Local / CI Configuration |
| CVSS Score | 7.8 (High) |
| Exploit Status | Proof of Concept available |
| Impact | Remote Code Execution |
| Affected Component | phpunit/phpunit (JobRunner) |
Improper neutralization of argument delimiters in a command line string leads to argument injection.