{"id":"GHSA-vvj3-c3rp-c85p","summary":"PHPUnit Vulnerable to Unsafe Deserialization in PHPT Code Coverage Handling","details":"### Overview\n\nA vulnerability has been discovered involving unsafe deserialization of code coverage data in PHPT test execution. The vulnerability exists in the `cleanupForCoverage()` method, which deserializes code coverage files without validation, potentially allowing remote code execution if malicious `.coverage` files are present prior to the execution of the PHPT test.\n\n### Technical Details\n\n**Affected Component:** PHPT test runner, method `cleanupForCoverage()`\n**Affected Versions:** \u003c= 8.5.51, \u003c= 9.6.32, \u003c= 10.5.61, \u003c= 11.5.49, \u003c= 12.5.7\n\n### Vulnerable Code Pattern\n\n```php\nif ($buffer !== false) {\n    // Unsafe call without restrictions\n    $coverage = @unserialize($buffer);\n}\n```\n\nThe vulnerability occurs when a `.coverage` file, which should not exist before test execution, is deserialized without the `allowed_classes` parameter restriction. An attacker with local file write access can place a malicious serialized object with a `__wakeup()` method into the file system, leading to arbitrary code execution during test runs with code coverage instrumentation enabled.\n\n### Attack Prerequisites and Constraints\n\nThis vulnerability requires **local file write access** to the location where PHPUnit stores or expects code coverage files for PHPT tests. This can occur through:\n\n* **CI/CD Pipeline Attacks:** A malicious pull request that places a `.coverage` file alongside test files, executed when the CI system runs tests using PHPUnit and collects code coverage information\n* **Local Development Environment:** An attacker with shell access or ability to write files to the project directory\n* **Compromised Dependencies:** A supply chain attack inserting malicious files into a package or monorepo\n\n**Critical Context:** Running test suites from unreviewed pull requests without isolated execution is inherently a code execution risk, independent of this specific vulnerability. This represents a broader class of [Poisoned Pipeline Execution (PPE) attacks](https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-04-Poisoned-Pipeline-Execution) affecting CI/CD systems.\n\n### Proposed Remediation Approach\n\nRather than just silently sanitizing the input via `['allowed_classes' =\u003e false]`, the maintainer has chosen to make the anomalous state explicit by treating pre-existing `.coverage` files for PHPT tests as an error condition.\n\n#### Rationale for Error-Based Approach:\n\n1. **Visibility Over Silence:** When an invariant is violated (a `.coverage` file existing before test execution), the error must be visible in CI/CD output, alerting operators to investigate the root cause rather than proceeding with sanitized input\n2. **Operational Security:** A `.coverage` file should never exist before tests run, coverage data is generated by executing tests, not sourced from artifacts. Its presence indicates:\n    * A malicious actor placed it intentionally\n    * Build artifacts from a previous run contaminated the environment\n    * An unexpected filesystem state requiring investigation\n3. **Defense-in-Depth Principle:** Protecting a single deserialization call does not address the fundamental attack surface. Proper mitigations for PPE attacks lie outside PHPUnit's scope:\n    * Isolate CI/CD runners (ephemeral, containerized environments)\n    * Restrict code execution on protected branches\n    * Scan pull requests and artifacts for tampering\n    * Use branch protection rules to prevent unreviewed code execution\n\n### Severity Classification\n\n* **Attack Vector (AV):** Local (L) — requires write access to the file system where tests execute\n* **Attack Complexity (AC):** Low (L) — exploitation is straightforward once the malicious file is placed\n* **Privileges Required (PR):** Low (L) — PR submitter status or contributor role provides sufficient access\n* **User Interaction (UI):** None (N) — automatic execution during standard test execution\n* **Scope (S):** Unchanged (U) — impact remains within the affected test execution context\n* **Confidentiality Impact (C):** High (H) — full remote code execution enables complete system compromise\n* **Integrity Impact (I):** High (H) — arbitrary code execution allows malicious modifications\n* **Availability Impact (A):** High (H) — full code execution permits denial-of-service actions\n\n### Mitigating Factors (Environmental Context)\n\nOrganizations can reduce the effective risk of this vulnerability through proper CI/CD configuration:\n\n* **Ephemeral Runners:** Use containerized, single-use CI/CD runners that discard filesystem state between runs\n* **Code Review Enforcement:** Require human review and approval before executing code from pull requests\n* **Branch Protection:** Enforce branch protection rules that block unreviewed code execution\n* **Artifact Isolation:** Separate build artifacts from source; never reuse artifacts across independent builds\n* **Access Control:** Limit file write permissions in CI environments to authenticated, trusted actors\n\n### Fixed Behaviour\n\nWhen a `.coverage` file is detected for a PHPT test prior to execution, PHPUnit will emit a clear error message identifying the anomalous state. This ensures:\n\n* **Visibility:** The error appears prominently in CI/CD output and test logs\n* **Investigation:** Operations teams can investigate the root cause (potential tampering, environment contamination)\n* **Fail-Fast Semantics:** Test execution stops rather than proceeding with an unexpected state\n\n### Recommendation\n\n**Update to the patched version immediately** if a project runs PHPT tests using PHPUnit with coverage instrumentation in any CI/CD environment that executes code from external contributors. Additionally, audit the project's CI/CD configuration to ensure:\n\n* Pull requests from forks or untrusted sources execute in isolated environments\n* Branch protection rules require human review before code execution\n* CI/CD runners are ephemeral and discarded after each build\n* Build artifacts are not reused across independent runs without validation","aliases":["CVE-2026-24765"],"modified":"2026-05-05T15:55:47.547809Z","published":"2026-01-27T22:26:22Z","database_specific":{"github_reviewed_at":"2026-01-27T22:26:22Z","nvd_published_at":"2026-01-27T22:15:56Z","cwe_ids":["CWE-502"],"severity":"HIGH","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/security/advisories/GHSA-vvj3-c3rp-c85p"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-24765"},{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/commit/3141742e00620e2968d3d2e732d320de76685fda"},{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/commit/613d142f5a8471ca71623ce5ca2795f79248329e"},{"type":"WEB","url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/phpunit/phpunit/CVE-2026-24765.yaml"},{"type":"PACKAGE","url":"https://github.com/sebastianbergmann/phpunit"},{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/releases/tag/10.5.63"},{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/releases/tag/11.5.50"},{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/releases/tag/12.5.8"},{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/releases/tag/8.5.52"},{"type":"WEB","url":"https://github.com/sebastianbergmann/phpunit/releases/tag/9.6.33"},{"type":"WEB","url":"https://lists.debian.org/debian-lts-announce/2026/02/msg00009.html"},{"type":"WEB","url":"https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-04-Poisoned-Pipeline-Execution"}],"affected":[{"package":{"name":"phpunit/phpunit","ecosystem":"Packagist","purl":"pkg:composer/phpunit/phpunit"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"8.5.52"}]}],"versions":["3.7.0","3.7.1","3.7.10","3.7.11","3.7.12","3.7.13","3.7.14","3.7.15","3.7.16","3.7.17","3.7.18","3.7.19","3.7.2","3.7.20","3.7.21","3.7.22","3.7.23","3.7.24","3.7.25","3.7.26","3.7.27","3.7.28","3.7.29","3.7.3","3.7.30","3.7.31","3.7.32","3.7.33","3.7.34","3.7.35","3.7.36","3.7.37","3.7.38","3.7.4","3.7.5","3.7.6","3.7.7","3.7.8","3.7.9","4.0.0","4.0.1","4.0.10","4.0.11","4.0.12","4.0.13","4.0.14","4.0.15","4.0.16","4.0.17","4.0.18","4.0.19","4.0.2","4.0.20","4.0.3","4.0.4","4.0.5","4.0.6","4.0.7","4.0.8","4.0.9","4.1.0","4.1.1","4.1.2","4.1.3","4.1.4","4.1.5","4.1.6","4.2.0","4.2.1","4.2.2","4.2.3","4.2.4","4.2.5","4.2.6","4.3.0","4.3.1","4.3.2","4.3.3","4.3.4","4.3.5","4.4.0","4.4.1","4.4.2","4.4.3","4.4.4","4.4.5","4.5.0","4.5.1","4.6.0","4.6.1","4.6.10","4.6.2","4.6.3","4.6.4","4.6.5","4.6.6","4.6.7","4.6.8","4.6.9","4.7.0","4.7.1","4.7.2","4.7.3","4.7.4","4.7.5","4.7.6","4.7.7","4.8.0","4.8.1","4.8.10","4.8.11","4.8.12","4.8.13","4.8.14","4.8.15","4.8.16","4.8.17","4.8.18","4.8.19","4.8.2","4.8.20","4.8.21","4.8.22","4.8.23","4.8.24","4.8.25","4.8.26","4.8.27","4.8.28","4.8.29","4.8.3","4.8.30","4.8.31","4.8.32","4.8.33","4.8.34","4.8.35","4.8.36","4.8.4","4.8.5","4.8.6","4.8.7","4.8.8","4.8.9","5.0.0","5.0.1","5.0.10","5.0.2","5.0.3","5.0.4","5.0.5","5.0.6","5.0.7","5.0.8","5.0.9","5.1.0","5.1.1","5.1.2","5.1.3","5.1.4","5.1.5","5.1.6","5.1.7","5.2.0","5.2.1","5.2.10","5.2.11","5.2.12","5.2.2","5.2.3","5.2.4","5.2.5","5.2.6","5.2.7","5.2.8","5.2.9","5.3.0","5.3.1","5.3.2","5.3.3","5.3.4","5.3.5","5.4.0","5.4.1","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.4.7","5.4.8","5.5.0","5.5.1","5.5.2","5.5.3","5.5.4","5.5.5","5.5.6","5.5.7","5.6.0","5.6.1","5.6.2","5.6.3","5.6.4","5.6.5","5.6.6","5.6.7","5.6.8","5.7.0","5.7.1","5.7.10","5.7.11","5.7.12","5.7.13","5.7.14","5.7.15","5.7.16","5.7.17","5.7.18","5.7.19","5.7.2","5.7.20","5.7.21","5.7.22","5.7.23","5.7.24","5.7.25","5.7.26","5.7.27","5.7.3","5.7.4","5.7.5","5.7.6","5.7.7","5.7.8","5.7.9","6.0.0","6.0.1","6.0.10","6.0.11","6.0.12","6.0.13","6.0.2","6.0.3","6.0.4","6.0.5","6.0.6","6.0.7","6.0.8","6.0.9","6.1.0","6.1.1","6.1.2","6.1.3","6.1.4","6.2.0","6.2.1","6.2.2","6.2.3","6.2.4","6.3.0","6.3.1","6.4.0","6.4.1","6.4.2","6.4.3","6.4.4","6.5.0","6.5.1","6.5.10","6.5.11","6.5.12","6.5.13","6.5.14","6.5.2","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5.8","6.5.9","7.0.0","7.0.1","7.0.2","7.0.3","7.1.0","7.1.1","7.1.2","7.1.3","7.1.4","7.1.5","7.2.0","7.2.1","7.2.2","7.2.3","7.2.4","7.2.5","7.2.6","7.2.7","7.3.0","7.3.1","7.3.2","7.3.3","7.3.4","7.3.5","7.4.0","7.4.1","7.4.2","7.4.3","7.4.4","7.4.5","7.5.0","7.5.1","7.5.10","7.5.11","7.5.12","7.5.13","7.5.14","7.5.15","7.5.16","7.5.17","7.5.18","7.5.19","7.5.2","7.5.20","7.5.3","7.5.4","7.5.5","7.5.6","7.5.7","7.5.8","7.5.9","8.0.0","8.0.1","8.0.2","8.0.3","8.0.4","8.0.5","8.0.6","8.1.0","8.1.1","8.1.2","8.1.3","8.1.4","8.1.5","8.1.6","8.2.0","8.2.1","8.2.2","8.2.3","8.2.4","8.2.5","8.3.0","8.3.1","8.3.2","8.3.3","8.3.4","8.3.5","8.4.0","8.4.1","8.4.2","8.4.3","8.5.0","8.5.1","8.5.10","8.5.11","8.5.12","8.5.13","8.5.14","8.5.15","8.5.16","8.5.17","8.5.18","8.5.19","8.5.2","8.5.20","8.5.21","8.5.22","8.5.23","8.5.24","8.5.25","8.5.26","8.5.27","8.5.28","8.5.29","8.5.3","8.5.30","8.5.31","8.5.32","8.5.33","8.5.34","8.5.35","8.5.36","8.5.37","8.5.38","8.5.39","8.5.4","8.5.40","8.5.41","8.5.42","8.5.43","8.5.44","8.5.45","8.5.46","8.5.47","8.5.48","8.5.49","8.5.5","8.5.50","8.5.51","8.5.6","8.5.7","8.5.8","8.5.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-vvj3-c3rp-c85p/GHSA-vvj3-c3rp-c85p.json"}},{"package":{"name":"phpunit/phpunit","ecosystem":"Packagist","purl":"pkg:composer/phpunit/phpunit"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"9.0.0"},{"fixed":"9.6.33"}]}],"versions":["9.0.0","9.0.1","9.0.2","9.1.0","9.1.1","9.1.2","9.1.3","9.1.4","9.1.5","9.2.0","9.2.1","9.2.2","9.2.3","9.2.4","9.2.5","9.2.6","9.3.0","9.3.1","9.3.10","9.3.11","9.3.2","9.3.3","9.3.4","9.3.5","9.3.6","9.3.7","9.3.8","9.3.9","9.4.0","9.4.1","9.4.2","9.4.3","9.4.4","9.5.0","9.5.1","9.5.10","9.5.11","9.5.12","9.5.13","9.5.14","9.5.16","9.5.17","9.5.18","9.5.19","9.5.2","9.5.20","9.5.21","9.5.22","9.5.23","9.5.24","9.5.25","9.5.26","9.5.27","9.5.28","9.5.3","9.5.4","9.5.5","9.5.6","9.5.7","9.5.8","9.5.9","9.6.0","9.6.1","9.6.10","9.6.11","9.6.12","9.6.13","9.6.14","9.6.15","9.6.16","9.6.17","9.6.18","9.6.19","9.6.2","9.6.20","9.6.21","9.6.22","9.6.23","9.6.24","9.6.25","9.6.26","9.6.27","9.6.28","9.6.29","9.6.3","9.6.30","9.6.31","9.6.32","9.6.4","9.6.5","9.6.6","9.6.7","9.6.8","9.6.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-vvj3-c3rp-c85p/GHSA-vvj3-c3rp-c85p.json"}},{"package":{"name":"phpunit/phpunit","ecosystem":"Packagist","purl":"pkg:composer/phpunit/phpunit"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"10.0.0"},{"fixed":"10.5.62"}]}],"versions":["10.0.0","10.0.1","10.0.10","10.0.11","10.0.12","10.0.13","10.0.14","10.0.15","10.0.16","10.0.17","10.0.18","10.0.19","10.0.2","10.0.3","10.0.4","10.0.5","10.0.6","10.0.7","10.0.8","10.0.9","10.1.0","10.1.1","10.1.2","10.1.3","10.2.0","10.2.1","10.2.2","10.2.3","10.2.4","10.2.5","10.2.6","10.2.7","10.3.0","10.3.1","10.3.2","10.3.3","10.3.4","10.3.5","10.4.0","10.4.1","10.4.2","10.5.0","10.5.1","10.5.10","10.5.11","10.5.12","10.5.13","10.5.14","10.5.15","10.5.16","10.5.17","10.5.18","10.5.19","10.5.2","10.5.20","10.5.21","10.5.22","10.5.23","10.5.24","10.5.25","10.5.26","10.5.27","10.5.28","10.5.29","10.5.3","10.5.30","10.5.31","10.5.32","10.5.33","10.5.34","10.5.35","10.5.36","10.5.37","10.5.38","10.5.39","10.5.4","10.5.40","10.5.41","10.5.42","10.5.43","10.5.44","10.5.45","10.5.46","10.5.47","10.5.48","10.5.49","10.5.5","10.5.50","10.5.51","10.5.52","10.5.53","10.5.54","10.5.55","10.5.56","10.5.57","10.5.58","10.5.59","10.5.6","10.5.60","10.5.61","10.5.7","10.5.8","10.5.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-vvj3-c3rp-c85p/GHSA-vvj3-c3rp-c85p.json"}},{"package":{"name":"phpunit/phpunit","ecosystem":"Packagist","purl":"pkg:composer/phpunit/phpunit"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"11.0.0"},{"fixed":"11.5.50"}]}],"versions":["11.0.0","11.0.1","11.0.10","11.0.2","11.0.3","11.0.4","11.0.5","11.0.6","11.0.7","11.0.8","11.0.9","11.1.0","11.1.1","11.1.2","11.1.3","11.2.0","11.2.1","11.2.2","11.2.3","11.2.4","11.2.5","11.2.6","11.2.7","11.2.8","11.2.9","11.3.0","11.3.1","11.3.2","11.3.3","11.3.4","11.3.5","11.3.6","11.4.0","11.4.1","11.4.2","11.4.3","11.4.4","11.5.0","11.5.1","11.5.10","11.5.11","11.5.12","11.5.13","11.5.14","11.5.15","11.5.16","11.5.17","11.5.18","11.5.19","11.5.2","11.5.20","11.5.21","11.5.22","11.5.23","11.5.24","11.5.25","11.5.26","11.5.27","11.5.28","11.5.29","11.5.3","11.5.30","11.5.31","11.5.32","11.5.33","11.5.34","11.5.35","11.5.36","11.5.37","11.5.38","11.5.39","11.5.4","11.5.40","11.5.41","11.5.42","11.5.43","11.5.44","11.5.45","11.5.46","11.5.47","11.5.48","11.5.49","11.5.5","11.5.6","11.5.7","11.5.8","11.5.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-vvj3-c3rp-c85p/GHSA-vvj3-c3rp-c85p.json"}},{"package":{"name":"phpunit/phpunit","ecosystem":"Packagist","purl":"pkg:composer/phpunit/phpunit"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"12.0.0"},{"fixed":"12.5.8"}]}],"versions":["12.0.0","12.0.1","12.0.10","12.0.2","12.0.3","12.0.4","12.0.5","12.0.6","12.0.7","12.0.8","12.0.9","12.1.0","12.1.1","12.1.2","12.1.3","12.1.4","12.1.5","12.1.6","12.2.0","12.2.1","12.2.2","12.2.3","12.2.4","12.2.5","12.2.6","12.2.7","12.2.8","12.2.9","12.3.0","12.3.1","12.3.10","12.3.11","12.3.12","12.3.13","12.3.14","12.3.15","12.3.2","12.3.3","12.3.4","12.3.5","12.3.6","12.3.7","12.3.8","12.3.9","12.4.0","12.4.1","12.4.2","12.4.3","12.4.4","12.4.5","12.5.0","12.5.1","12.5.2","12.5.3","12.5.4","12.5.5","12.5.6","12.5.7"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-vvj3-c3rp-c85p/GHSA-vvj3-c3rp-c85p.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}]}