
Differential-Proof-of-Concept für CVE-2026-40176, das eine OS-Befehlsinjektion im Perforce-Treiber von Composer über eine bösartige Repository-URL demonstriert, mit automatisiertem A/B-Testing gegen betroffene und behobene Versionen.
Ein eigenständiger, OOP-orientierter PHP-Proof-of-Concept, der eine Befehlsinjektions-Schwachstelle im Perforce-Repository-Treiber von Composer demonstriert und differential verifiziert.
Der PoC führt dieselbe bösartige composer.json gegen zwei Composer-Binaries aus — eine betroffene Version (2.9.5) und eine korrigierte Version (2.9.6) — und beweist den Fehler durch Beobachtung eines Nebeneffekts (eine Markierungsdatei, die von einem injizierten Shell-Befehl geschrieben wird), der bei der betroffenen Version auftritt, bei der korrigierten jedoch nicht.
⚠️ Nur für autorisierte Sicherheitsforschung und defensive Tests. Siehe Verantwortungsvolle Nutzung.
| CVE | CVE-2026-40176 |
| Komponente | Composer — Perforce (perforce) Repository-/VCS-Treiber |
| Klasse | Betriebssystem-Befehlsinjektion über eine vom Angreifer kontrollierte Repository-URL |
| Angriffsfläche | Eine composer.json mit einem manipulierten repositories-Eintrag (type: perforce) |
| Betroffen | Composer 2.9.5 |
| Behoben | Composer 2.9.6 |
| Auslöser | Auflösen/Aktualisieren von Abhängigkeiten (composer update) gegen das bösartige Manifest |
| Auswirkung | Beliebige Befehlsausführung auf dem Rechner, auf dem Composer läuft |
| PoC-Sprache | PHP (einzelne Datei, keine externen Abhängigkeiten) |
Composer kann Pakete aus verschiedenen Versionskontrollsystemen auflösen. Für Perforce wird das Repository durch eine p4://-URL identifiziert, die Host, Port und Benutzer/Stream kodiert. Wenn der Perforce-Treiber von Composer die zugrunde liegende p4-Befehlszeile aufbaut, werden Felder aus der vom Angreifer kontrollierten URL nicht ausreichend bereinigt, bevor sie an eine Shell übergeben werden.
Da der Autor des Manifests die Repository-URL vollständig kontrolliert, kann ein Angreifer, der ein Opfer dazu bringt, composer update/composer install gegen eine bösartige composer.json auszuführen (z. B. eine vergiftete Abhängigkeit, ein feindliches Repository oder einen CI-Job, der nicht vertrauenswürdige Projektdateien verarbeitet), aus dem vorgesehenen p4-Aufruf ausbrechen und beliebige Betriebssystembefehle mit den Rechten des Composer-Prozesses ausführen.
Dies gehört zur gleichen Familie wie historische Argument-Injektionsprobleme bei Composer-VCS-Treibern, bei denen URL-/Branch-/Stream-Werte unescaped in Shell-Befehle einfließen. Composer 2.9.6 härtet den Perforce-Treiber so, dass der injizierte Payload nicht mehr ausgeführt wird.
Die maßgebliche Beschreibung des hier demonstrierten Verhaltens ist der PoC-Quellcode selbst (
CVE202640176Test.php); konsultieren Sie das offizielle Advisory und das Composer-Changelog für Details zum Upstream-Fix.
Der PoC ist eine einzelne Klasse, CVE202640176Test, die ein kontrolliertes A/B-Experiment (differentiell) durchführt:
--version bei beiden Composer-Binaries ab (betroffen: 2.9.5, korrigiert: 2.9.6) und bricht frühzeitig ab, wenn eines nicht aufgerufen werden kann.composer.json, deren repositories-Abschnitt einen perforce-Eintrag mit einer bösartigen p4://-URL enthält, die einen injizierten Shell-Payload trägt.composer update in diesem Verzeichnis aus.finally-Block stellt immer die ursprüngliche composer.json im Projektverzeichnis wieder her.PASS nur aus, wenn der betroffene Lauf den Nebeneffekt zeigt und der korrigierte Lauf ihn nicht zeigt.Für jeden Lauf prüft validateRun() drei Dinge:
| Prüfung | Was sie beweist |
|---|---|
| Markierungsdatei existiert & enthält die Lauf-ID | Der injizierte touch/echo-Payload wurde tatsächlich ausgeführt — d. h. die Befehlsinjektion war erfolgreich. |
Composer-Ausgabe erwähnt p4 | Der Perforce-Treiber-Codepfad wurde erreicht (der Payload wurde von der richtigen Komponente verarbeitet, nicht von einem unabhängigen Schritt). |
| Geparste Composer-Version == erwartet | Das richtige Binary (2.9.5 vs. 2.9.6) wurde ausgeführt. |
Ein Lauf ist nur dann „OK", wenn alle drei Prüfungen bestehen. Der Gesamttest besteht, wenn der betroffene Lauf OK ist und der korrigierte Lauf nicht — die genaue Signatur einer echten Schwachstelle, die anschließend gepatcht wurde.
Die bösartige Repository-URL wird in writeComposerJson() erstellt:
p4://127.0.0.1:1666:attacker_user;touch <marker> && echo '<runId>' > <marker>:client_test
Aufgeschlüsselt:
p4://127.0.0.1:1666:attacker_user — eine wohlgeformt aussehende Perforce-URL (Host, Port 1666, Benutzer).;touch <marker> && echo '<runId>' > <marker> — die injizierten Shell-Befehle. Das führende ; beendet den vorgesehenen p4-Befehl; touch erstellt die Markierungsdatei und echo '<runId>' > <marker> schreibt die eindeutige Lauf-ID hinein, damit der PoC bestätigen kann, dass der Payload (und nicht ein anderer Prozess) die Datei erzeugt hat.:client_test — nachgestellter Text, um den Rest der URL-Interpretation plausibel zu halten.Beim betroffenen Treiber werden die Shell-Metazeichen berücksichtigt und die Markierungsdatei erstellt. Beim korrigierten Treiber wird der Wert ordnungsgemäß escaped/in Anführungszeichen gesetzt, sodass dieselbe Zeichenkette als inerte Daten behandelt wird und keine Markierung erscheint.
Hinweis: Der PoC verwendet eine eindeutige, mit Zeitstempel versehene Lauf-ID und schreibt seine Markierung in ein isoliertes temporäres Verzeichnis, sodass der Payload harmlos und selbstreinigend statt destruktiv ist.
2.9.5 (betroffen)2.9.6 (korrigiert)exec() führt cd … && php … aus). Für Linux/macOS ausgelegt.composer.json im Projektverzeichnis (wird beim Start gelesen, in jeden temporären Lauf kopiert und danach wiederhergestellt).In der Regel benötigen Sie keinen laufenden Perforce-Server: Die Schwachstelle liegt darin, wie Composer die
p4-Befehlszeile aufbaut, und der injizierte Payload läuft vor/um jede echtep4-Verbindung. Composer protokolliert möglicherweise einen Perforce-Verbindungsfehler — das ist erwartet und beeinträchtigt den Markierungsdatei-Beweis nicht.
PoC klonen / ablegen in einem Arbeitsverzeichnis.
Eine composer.json bereitstellen im selben Verzeichnis wie der PoC. Eine minimale genügt:
{
"name": "research/cve-2026-40176-poc",
"description": "Base manifest for the CVE-2026-40176 differential PoC",
"require": {}
}
Die beiden Composer-Binaries besorgen und dort ablegen, wo der PoC sie erwartet (Standardwerte gezeigt):
/usr/local/bin/composer-2.9.5.phar # affected
/usr/local/bin/composer-2.9.6.phar # fixed
Sie können bestimmte Composer-Versionen aus dem offiziellen Archiv herunterladen, z. B.:
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
Wenn Ihre Pfade abweichen, bearbeiten Sie die beiden Konstruktor-Argumente am Ende von
CVE202640176Test.php.
php CVE202640176Test.php
Das Harness führt beide Composer-Versionen nacheinander aus und gibt ein endgültiges Urteil aus. Die ursprüngliche composer.json wird automatisch wiederhergestellt, auch wenn ein Lauf fehlschlägt (die Arbeit findet in Wegwerf-Temp-Verzeichnissen statt).
Das Repo enthält ein containerisiertes Labor, das die Umgebung exakt nachbildet: eine PHP-CLI-Laufzeit plus die beiden festgelegten Composer-Versionen an den vom PoC erwarteten Pfaden, zur Laufzeit vollständig netzwerkisoliert.
docker compose run --rm poc
Dies erstellt cve-2026-40176-lab:latest (lädt Composer 2.9.5 und 2.9.6 herunter und verifiziert während des Builds jeweils --version) und führt den differentiellen Test in einem unprivilegierten Container ohne Zugriff nach außen aus.
Was das Labor garantiert:
poc-Dienst läuft auf einem internal-Bridge-Netzwerk (kein Host-/Internet-Egress), mit cap_drop: ALL und no-new-privileges. Der Injektions-Payload bleibt eingedämmt.Die Versionen neu festlegen (muss mit den beiden Konstruktor-Pfaden im PoC synchron bleiben) über Build-Argumente:
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
Optional — Live-Perforce-Server. Ein p4d-Dienst ist unter dem full-lab-Profil verfügbar (docker compose --profile full-lab up). Der markierungsbasierte Beweis benötigt ihn nicht; er existiert für Forscher, die einen echten p4://-Endpunkt wünschen. Beachten Sie, dass der PoC-Payload auf 127.0.0.1:1666 zielt, sodass ein Routing über einen separaten p4d-Container erfordert, die PoC-URL auf den Host p4d zu richten.
Ehrliches Ergebnis: Gegen die echten, veröffentlichten Composer-Versionen
2.9.5und2.9.6löst der PoC derzeit nicht aus, und das Labor meldetINCONCLUSIVE / FAIL.
Die Ausführung des betroffenen Composer (2.9.5) gegen das bösartige Manifest des PoC wirft innerhalb von Composer bevor irgendein p4-/Shell-Befehl aufgebaut wird:
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
PerforceDriver::initialize() liest zuerst $this->repoConfig['depot'], aber der Repository-Eintrag des PoC liefert nur type und url (keinen depot-Schlüssel). Der Treiber bricht an dieser Stelle ab, sodass der injizierte ;touch <marker>-Payload in der URL nie erreicht wird und keine Markierung erstellt wird. Netzwerkisolation ist nicht die Ursache — derselbe Fehler tritt mit vollem Egress auf.
Was das bedeutet:
INCONCLUSIVE-Ergebnis ist eine Eigenschaft des PoC-Payloads, nicht der Umgebung.depot-Schlüssel enthalten (und realistischerweise einen echten p4d-Endpunkt unter dem full-lab-Profil). Den Payload bis zu diesem Punkt zu verfeinern ist Exploit-Entwicklung jenseits von „das Labor aufsetzen" und hier bewusst außerhalb des Rahmens gelassen.Die unten stehende „Erwartete Ausgabe" ist das beabsichtigte/idealisierte Ergebnis des PoC, zur Referenz beibehalten; es ist nicht das, was der aktuelle Payload gegen den echten Treiber produziert.
Eine erfolgreiche Demonstration sieht ungefähr so aus (Pfade und IDs variieren):
=== 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) ===
Ein Exit-Code ungleich Null von Composer ist normal — composer update schlägt letztlich beim Abrufen des (fiktiven) Pakets fehl. Der Beweis ist die Markierungsdatei, nicht der Exit-Status von Composer.
| Ergebnis | Bedeutung |
|---|---|
| PASS (betroffen erfolgreich, korrigiert fehlgeschlagen) | Bestätigt: 2.9.5 führte den injizierten Befehl aus, 2.9.6 nicht. Sowohl die Schwachstelle als auch ihr Fix wurden reproduziert. |
| INCONCLUSIVE / FAIL | Eine oder mehrere Prüfungen stimmten nicht überein. Untersuchen Sie die ✓/✗-Zeilen pro Lauf: falscher Binary-Pfad, Versionsabweichung, fehlende Markierung beim betroffenen Lauf (Umgebungs-/Escaping-Unterschied) oder eine unerwartet erzeugte Markierung im korrigierten Lauf. |
Häufige Ursachen für ein nicht schlüssiges Ergebnis:
PerforceDriver.php:40 mit Undefined array key "depot" ab — der Repository-Konfiguration fehlt ein depot-Schlüssel, sodass Composer den Pfad zur p4-Befehls-Konstruktion nie erreicht. Genau das passiert mit dem aktuellen Payload des PoC gegen die echten 2.9.5/2.9.6 (siehe Reproduktionsstatus).2.9.5 / 2.9.6.exec() ist in einer Sandbox/deaktiviert.p4 (Treiberpfad nicht erreicht).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
Der gesamte PoC ist eine Datei:
__construct() — speichert die beiden Binary-Pfade, erzeugt eine mit Zeitstempel versehene Lauf-ID, macht eine Momentaufnahme der ursprünglichen composer.json.run() — orchestriert Vorprüfung, betroffenen Lauf, korrigierten Lauf, Wiederherstellung und Urteil.prepareTempDir() — erstellt pro Lauf ein isoliertes Arbeitsverzeichnis.writeComposerJson() — erstellt das bösartige Manifest mit der injizierten p4://-URL.runComposer() — führt composer update aus (Argumente mit escapeshellarg() escaped) und erfasst Ausgabe + Exit-Code.validateRun() — prüft Version, Markierungsdatei und Perforce-Treiber-Aktivität.preflightVersion() — liest --version eines angegebenen Binaries.composer.json wird unabhängig vom Ergebnis in einem finally-Block wiederhergestellt.escapeshellarg() (damit der PoC nicht versehentlich in seine eigenen exec()-Aufrufe injiziert). Die Schwachstelle liegt eine Ebene tiefer — darin, wie Composer selbst den p4-Befehl aufbaut — genau das ist das Ziel des Payloads.touch/echo in ein eindeutiges Temp-Marker aus, wodurch der PoC wiederholt ohne Nebenwirkungen auf den Host sicher ausgeführt werden kann.exec("cd … && php …")-Muster und der ;/&&-Payload setzen eine Unix-ähnliche Shell voraus; Windows wird nicht wie vorgesehen unterstützt.mkdir-Race / Berechtigungen. Temp-Verzeichnisse werden mit Modus 0777 erstellt; verschärfen Sie dies, wenn Sie in einer gemeinsamen Umgebung arbeiten.Composer X.Y.Z aus der Ausgabe; ungewöhnliche Composer-Banner könnten den Abgleich verhindern.Dieses Repository existiert, um CVE-2026-40176 zu verstehen und sich dagegen zu verteidigen.
composer install/update niemals auf nicht vertrauenswürdigen composer.json-Dateien aus (z. B. in CI-Pipelines, die Projektquellen Dritter verarbeiten) ohne Isolation.2.9.5- / 2.9.6-PHARs)2.9.6-Changelog (konsultieren Sie den Sicherheits-Tracker Ihrer Distribution / die GitHub Advisory Database für die maßgeblichen Fix-Details)Autor: Ikarolaborda · PoC datiert 2026-06-10.