Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-40176 | Kitploit
Tools/GitHubGitHub/ikarolaborda/cve-2026-40176
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingCommand and ControlLearning & Education
GitHubikarolaborda/cve-2026-40176

CVE-2026-40176

View Repository
2 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-40176 — Composer Perforce Driver Command Injection (Proof of Concept)

A self-contained, OOP-style PHP proof of concept that demonstrates and differentially verifies a command-injection vulnerability in Composer's Perforce repository driver.

The PoC runs the same malicious composer.json against two Composer binaries — an affected release (2.9.5) and a fixed release (2.9.6) — and proves the bug by observing a side effect (a marker file written by an injected shell command) that fires on the affected version but not on the fixed one.

⚠️ For authorized security research and defensive testing only. See Responsible Use.


Table of Contents

  • Summary
  • The Vulnerability
  • How the PoC Works
  • The Injection Payload
  • Requirements
  • Setup
  • Usage
  • Expected Output
  • Interpreting the Result
  • Project Structure
  • Design Notes
  • Limitations & Known Issues
  • Responsible Use
  • References

Summary


The Vulnerability

Composer can resolve packages from several version-control systems. For Perforce, the repository is identified by a p4:// URL that encodes the host, port and user/stream. When Composer's Perforce driver builds the underlying p4 command line, fields taken from the attacker-controlled URL are not sufficiently sanitized before being handed to a shell.

Because the manifest author fully controls the repository URL, an attacker who can get a victim to run composer update/composer install against a malicious composer.json (for example, a poisoned dependency, a hostile repository, or a CI job processing untrusted project files) can break out of the intended p4 invocation and execute arbitrary OS commands with the privileges of the Composer process.

This belongs to the same family as historical Composer VCS-driver argument-injection issues, where URL/branch/stream values flow into shell commands without escaping. Composer 2.9.6 hardens the Perforce driver so the injected payload no longer executes.

The authoritative description of the behavior demonstrated here is the PoC source itself (CVE202640176Test.php); consult the official advisory and Composer changelog for the upstream fix details.


How the PoC Works

The PoC is a single class, CVE202640176Test, that performs a controlled A/B (differential) experiment:

  1. Preflight — queries --version on both the affected (2.9.5) and fixed (2.9.6) Composer binaries and aborts early if either cannot be invoked.
  2. Affected run (2.9.5)
    • Creates an isolated temp directory under the system temp path.
    • Writes a composer.json whose repositories section contains a perforce entry with a malicious p4:// URL carrying an injected shell payload.
    • Runs composer update in that directory.
    • Validates the result.
  3. Fixed run (2.9.6) — repeats the exact same steps against the patched binary.
  4. Restore — a finally block always restores the original composer.json in the project directory.
  5. Verdict — prints PASS only when the affected run shows the side effect and the fixed run does not.

Validation (what counts as "exploited")

For each run, validateRun() checks three things:

CheckWhat it proves

A run is "OK" only when all three pass. The overall test passes when the affected run is OK and the fixed run is not — the precise signature of a real vulnerability that was subsequently patched.


The Injection Payload (Explained)

The malicious repository URL is built in writeComposerJson():

root@kitploit:~
p4://127.0.0.1:1666:attacker_user;touch <marker> && echo '<runId>' > <marker>:client_test

Breaking it down:

  • p4://127.0.0.1:1666:attacker_user — a well-formed-looking Perforce URL (host, port 1666, user).
  • ;touch <marker> && echo '<runId>' > <marker> — the injected shell commands. The leading ; terminates the intended p4 command; touch creates the marker file, and echo '<runId>' > <marker> writes the unique run ID into it so the PoC can confirm the payload (and not some unrelated process) produced the file.
  • :client_test — trailing text to keep the rest of the URL parsing plausible.

On the affected driver the shell metacharacters are honored and the marker file is created. On the fixed driver the value is properly escaped/quoted, so the same string is treated as inert data and no marker appears.

Note: the PoC uses a unique, timestamped run ID and writes its marker inside an isolated temp directory, so the payload is benign and self-cleaning rather than destructive.


Requirements

  • PHP 7.4+ (developed/tested against PHP 8.x CLI). The PoC itself uses only core functions — no Composer packages required to run the harness.
  • Two Composer binaries available as PHARs:
    • Composer 2.9.5 (affected)
    • Composer 2.9.6 (fixed)
  • A POSIX-like shell environment (exec() runs cd … && php …). Designed for Linux/macOS.
  • A base composer.json in the project directory (it is read at startup, copied into each temp run, and restored afterward).

You generally do not need a live Perforce server: the vulnerability is in how Composer builds the p4 command line, and the injected payload runs before/around any real p4 connection. Composer may log a Perforce connection error — that is expected and does not affect the marker-file proof.


Setup

  1. Clone / place the PoC in a working directory.

  2. Provide a composer.json in the same directory as the PoC. A minimal one is enough:

    root@kitploit:~
    {
      "name": "research/cve-2026-40176-poc",
      "description": "Base manifest for the CVE-2026-40176 differential PoC",
      "require": {}
    }
    
  3. Obtain the two Composer binaries and place them where the PoC expects them (defaults shown):

    root@kitploit:~
    /usr/local/bin/composer-2.9.5.phar   # affected
    /usr/local/bin/composer-2.9.6.phar   # fixed
    

    You can download specific Composer releases from the official archive, e.g.:

    root@kitploit:~
    curl -Lo /usr/local/bin/composer-2.9.5.phar https://getcomposer.org/download/2.9.5/composer.phar
    curl -Lo /usr/local/bin/composer-2.9.6.phar https://getcomposer.org/download/2.9.6/composer.phar
    

    If your paths differ, edit the two constructor arguments at the bottom of CVE202640176Test.php.


Usage

root@kitploit:~
php CVE202640176Test.php

The harness runs both Composer versions in turn and prints a final verdict. The original composer.json is restored automatically even if a run fails (the work happens in throwaway temp directories).


Run in Docker (recommended)

The repo ships a containerized lab that replicates the environment exactly: a PHP CLI runtime plus the two pinned Composer releases at the paths the PoC expects, fully network-isolated at runtime.

root@kitploit:~
docker compose run --rm poc

This builds cve-2026-40176-lab:latest (downloading Composer 2.9.5 and 2.9.6 and verifying each --version during the build) and runs the differential test inside an unprivileged, egress-free container.

What the lab guarantees:

  • Real binaries. Both Composer releases are fetched from the official archive and version-checked at build time — the build fails loudly if a pinned version is unavailable.
  • Isolation. The poc service runs on an internal bridge network (no host/internet egress), with cap_drop: ALL and no-new-privileges. The injection payload stays contained.
  • No host setup. No need to place PHARs on your host or hand-edit paths.

Repin the versions (must stay in sync with the two constructor paths in the PoC) via build args:

root@kitploit:~
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6

Optional — live Perforce server. A p4d service is available under the full-lab profile (docker compose --profile full-lab up). The marker-based proof does not need it; it exists for researchers who want a live p4:// endpoint. Note the PoC payload targets 127.0.0.1:1666, so routing through a separate p4d container requires pointing the PoC URL at host p4d.


Reproduction Status (observed)

Honest result: against the real, published Composer 2.9.5 and 2.9.6, the PoC does not currently fire, and the lab reports INCONCLUSIVE / FAIL.

Running the affected Composer (2.9.5) against the PoC's malicious manifest throws, inside Composer, before any p4/shell command is built:

root@kitploit:~
In PerforceDriver.php line 40:
  [ErrorException]
  Undefined array key "depot"

PerforceDriver::initialize() reads $this->repoConfig['depot'] first thing, but the PoC's repository entry supplies only type and url (no depot key). The driver aborts at that point, so the injected ;touch <marker> payload in the URL is never reached and no marker is created. Network isolation is not the cause — the same error occurs with full egress.

What this means:

  • The Docker lab itself is correct and faithfully runs the differential harness against the genuine affected/fixed binaries. The INCONCLUSIVE outcome is a property of the PoC payload, not the environment.
  • To exercise the actual Perforce command-construction path, the PoC's repository config would need at least a depot key (and realistically a live p4d endpoint under the full-lab profile). Refining the payload to that point is exploit development beyond "stand up the lab" and is intentionally left out of scope here.

The "Expected Output" below is the PoC's intended/idealized result, retained for reference; it is not what the current payload produces against the real driver.


Expected Output

A successful demonstration looks roughly like this (paths and IDs will vary):

root@kitploit:~
=== CVE-2026-40176 PoC started ===
- Composer 2.9.5 version: 2.9.5
- Composer 2.9.6 version: 2.9.6
Prepared temp dir: /tmp/cve20264176_5_20260610_142233
Written malicious composer.json to /tmp/cve20264176_5_20260610_142233
Running Composer in /tmp/cve20264176_5_20260610_142233…
- Parsed Composer version: 2.9.5
- Marker /tmp/cve20264176_5_.../poc_marker_5.txt created with expected ID.
- Output shows Perforce driver activity.
- Affected run exit code: 1
Prepared temp dir: /tmp/cve20264176_6_20260610_142233
Written malicious composer.json to /tmp/cve20264176_6_20260610_142233
Running Composer in /tmp/cve20264176_6_20260610_142233…
- Parsed Composer version: 2.9.6
✘ Marker file /tmp/cve20264176_6_.../poc_marker_6.txt not found.
- Output shows Perforce driver activity.
- Fixed   run exit code: 1

=== CVE-2026-40176 PoC finished ===
=== TEST RESULT: PASS (affected succeeded, fixed failed) ===

A non-zero Composer exit code is normal — composer update ultimately fails to fetch the (fake) package. The proof is the marker file, not Composer's exit status.


Interpreting the Result

ResultMeaning
PASS (affected succeeded, fixed failed)Confirmed: 2.9.5 executed the injected command, 2.9.6 did not. The vulnerability and its fix are both reproduced.
INCONCLUSIVE / FAILOne or more checks didn't line up. Inspect the per-run ✓/✗ lines: wrong binary path, version mismatch, the affected marker missing (environment/escaping difference), or the fixed run unexpectedly creating a marker.

Common causes of an inconclusive result:

  • The driver aborts at PerforceDriver.php:40 with Undefined array key "depot" — the repository config lacks a depot key, so Composer never reaches the p4 command-construction path. This is what happens with the PoC's current payload against the real 2.9.5/2.9.6 (see Reproduction Status).
  • Composer binary paths are wrong or the PHARs aren't actually 2.9.5 / 2.9.6.
  • The host shell or PHP exec() is sandboxed/disabled.
  • Composer output doesn't contain the literal string p4 (driver path not reached).

Project Structure

root@kitploit:~
CVE2026-40176/
├── CVE202640176Test.php   # The PoC: CVE202640176Test class + entry point
├── composer.json          # Base manifest the PoC reads/restores at runtime
├── Dockerfile             # Lab image: PHP CLI + pinned Composer 2.9.5 & 2.9.6
├── docker-compose.yml     # `poc` runner (+ optional `p4d` under full-lab profile)
├── .dockerignore          # Trims the build context
├── README.md              # This file
└── .gitignore             # Excludes local agent/tooling state

The entire PoC is one file:

  • __construct() — stores the two binary paths, mints a timestamped run ID, snapshots the original composer.json.
  • run() — orchestrates preflight, the affected run, the fixed run, restoration, and the verdict.
  • prepareTempDir() — creates an isolated working directory per run.
  • writeComposerJson() — builds the malicious manifest with the injected p4:// URL.
  • runComposer() — executes composer update (arguments escaped with escapeshellarg()) and captures output + exit code.
  • validateRun() — checks version, marker file, and Perforce-driver activity.
  • preflightVersion() — reads --version from a given binary.

Design Notes

  • Isolation & cleanup. Each run uses its own temp directory; the original composer.json is restored in a finally block regardless of outcome.
  • Harness vs. payload escaping. The harness escapes its own shell arguments with escapeshellarg() (so the PoC doesn't accidentally inject into its own exec() calls). The vulnerability lives one layer deeper — in how Composer itself builds the p4 command — which is exactly what the payload targets.
  • Differential proof. Running both the vulnerable and patched binaries in one pass removes ambiguity: the same input produces divergent behavior, which is far stronger evidence than a single positive observation.
  • Benign payload. The injected commands only touch/echo into a unique temp marker, making the PoC safe to run repeatedly without side effects to the host.

Limitations & Known Issues

  • Hard-coded binary paths. The two PHAR paths are passed inline at the bottom of the file. Edit them for your environment (or refactor to read from CLI args / environment variables).
  • POSIX assumption. The exec("cd … && php …") pattern and ;/&& payload assume a Unix-like shell; Windows is not supported as-is.
  • mkdir race / permissions. Temp directories are created with mode 0777; tighten if running in a shared environment.
  • Version detection is regex-based. It parses Composer X.Y.Z from output; unusual Composer banners could defeat the match.

Responsible Use

This repository exists to understand and defend against CVE-2026-40176.

  • Run it only against systems and Composer installations you own or are explicitly authorized to test.
  • The takeaway for defenders: upgrade Composer to 2.9.6 or later, and never run composer install/update on untrusted composer.json files (e.g., in CI pipelines that process third-party project source) without isolation.
  • Do not use the injection technique against systems you do not control. Unauthorized exploitation is illegal and unethical.

References

  • Composer — official project
  • Composer release archive (for fetching specific 2.9.5 / 2.9.6 PHARs)
  • Official CVE-2026-40176 advisory and the Composer 2.9.6 changelog (consult your distribution's security tracker / the GitHub Advisory Database for the authoritative fix details)
  • Background on Composer VCS-driver argument injection (the same vulnerability class), e.g. CVE-2021-29472

Author: Ikarolaborda · PoC dated 2026-06-10.

Download Tool
CVECVE-2026-40176
ComponentComposer — Perforce (perforce) repository/VCS driver
ClassOS command injection via attacker-controlled repository URL
Attack surfaceA composer.json containing a crafted repositories entry of type: perforce
AffectedComposer 2.9.5
FixedComposer 2.9.6
TriggerResolving/updating dependencies (composer update) against the malicious manifest
ImpactArbitrary command execution on the machine running Composer
PoC languagePHP (single file, no external dependencies)
Marker file exists & contains the run IDThe injected touch/echo payload actually executed — i.e. command injection succeeded.
Composer output mentions p4The Perforce driver code path was reached (the payload was processed by the right component, not some unrelated step).
Parsed Composer version == expectedThe correct binary (2.9.5 vs 2.9.6) was the one that ran.