
Preuve de concept différentielle pour CVE-2026-40176, démontrant une injection de commande OS dans le pilote Perforce de Composer via une URL de dépôt malveillante, avec des tests A/B automatisés contre les versions affectées et corrigées.
Une preuve de concept autonome, orientée objet en PHP, qui démontre et vérifie différentiellement une vulnérabilité d'injection de commande dans le pilote de dépôt Perforce de Composer.
Le PoC exécute le même composer.json malveillant avec deux binaires Composer — une version affectée (2.9.5) et une version corrigée (2.9.6) — et prouve le bogue en observant un effet secondaire (un fichier marqueur écrit par une commande shell injectée) qui se déclenche sur la version affectée mais pas sur la version corrigée.
⚠️ Pour la recherche en sécurité autorisée et les tests défensifs uniquement. Voir Utilisation responsable.
| CVE | CVE-2026-40176 |
| Composant | Composer — pilote de dépôt/VCS Perforce (perforce) |
| Classe | Injection de commande OS via une URL de dépôt contrôlée par l'attaquant |
| Surface d'attaque | Un composer.json contenant une entrée repositories de type perforce malveillante |
| Version affectée | Composer 2.9.5 |
| Version corrigée | Composer 2.9.6 |
| Déclencheur | Résolution/mise à jour des dépendances (composer update) sur le manifeste malveillant |
| Impact | Exécution de commande arbitraire sur la machine exécutant Composer |
| Langage du PoC | PHP (fichier unique, sans dépendances externes) |
Composer peut résoudre des paquets depuis plusieurs systèmes de gestion de versions. Pour Perforce, le dépôt est identifié par une URL p4:// qui encode l'hôte, le port et l'utilisateur/flux. Lorsque le pilote Perforce de Composer construit la ligne de commande p4 sous-jacente, les champs extraits de l'URL contrôlée par l'attaquant ne sont pas suffisamment assainis avant d'être transmis à un shell.
Comme l'auteur du manifeste contrôle totalement l'URL du dépôt, un attaquant qui parvient à faire exécuter composer update/composer install à une victime sur un composer.json malveillant (par exemple, une dépendance empoisonnée, un dépôt hostile, ou un job CI traitant des fichiers de projet non fiables) peut s'échapper de l'invocation prévue de p4 et exécuter des commandes OS arbitraires avec les privilèges du processus Composer.
Cette vulnérabilité appartient à la même famille que les problèmes historiques d'injection d'arguments dans les pilotes VCS de Composer, où les valeurs d'URL/branche/flux sont transmises à des commandes shell sans échappement. Composer 2.9.6 durcit le pilote Perforce afin que la charge utile injectée ne s'exécute plus.
La description faisant autorité du comportement démontré ici est le code source du PoC lui-même (
CVE202640176Test.php) ; consultez l'avis officiel et le journal des modifications de Composer pour les détails du correctif en amont.
Le PoC est une classe unique, CVE202640176Test, qui réalise une expérience A/B (différentielle) contrôlée :
--version sur les deux binaires Composer, affecté (2.9.5) et corrigé (2.9.6), et abandonne prématurément si l'un d'eux ne peut pas être invoqué.composer.json dont la section repositories contient une entrée perforce avec une URL p4:// malveillante portant une charge utile shell injectée.composer update dans ce répertoire.finally restaure toujours le composer.json original dans le répertoire du projet.PASS uniquement lorsque l'exécution affectée montre l'effet secondaire et que l'exécution corrigée ne le montre pas.Pour chaque exécution, validateRun() vérifie trois choses :
| Vérification | Ce qu'elle prouve |
|---|---|
| Le fichier marqueur existe et contient l'ID de l'exécution | La charge utile injectée touch/echo a réellement été exécutée — l'injection de commande a réussi. |
La sortie de Composer mentionne p4 | Le chemin de code du pilote Perforce a été atteint (la charge utile a été traitée par le bon composant, pas par une étape non liée). |
| La version de Composer analysée correspond à l'attendu | Le bon binaire (2.9.5 ou 2.9.6) est celui qui a été exécuté. |
Une exécution est "OK" uniquement si les trois conditions sont remplies. Le test global réussit lorsque l'exécution affectée est OK et que l'exécution corrigée ne l'est pas — la signature précise d'une véritable vulnérabilité qui a ensuite été corrigée.
L'URL de dépôt malveillante est construite dans writeComposerJson() :
p4://127.0.0.1:1666:attacker_user;touch <marqueur> && echo '<runId>' > <marqueur>:client_test
En détail :
p4://127.0.0.1:1666:attacker_user — une URL Perforce d'apparence bien formée (hôte, port 1666, utilisateur).;touch <marqueur> && echo '<runId>' > <marqueur> — les commandes shell injectées. Le ; initial termine la commande p4 prévue ; touch crée le fichier marqueur, et echo '<runId>' > <marqueur> écrit l'ID d'exécution unique dedans afin que le PoC puisse confirmer que la charge utile (et non un processus non lié) a produit le fichier.:client_test — texte de fin pour que le reste du parsing de l'URL reste plausible.Sur le pilote affecté, les métacaractères shell sont honorés et le fichier marqueur est créé. Sur le pilote corrigé, la valeur est correctement échappée/encadrée, donc la même chaîne est traitée comme des données inertes et aucun marqueur n'apparaît.
Remarque : le PoC utilise un ID d'exécution unique horodaté et écrit son marqueur dans un répertoire temporaire isolé, de sorte que la charge utile est bénigne et auto-nettoyante plutôt que destructive.
2.9.5 (affecté)2.9.6 (corrigé)exec() exécute cd … && php …). Conçu pour Linux/macOS.composer.json de base dans le répertoire du projet (il est lu au démarrage, copié dans chaque exécution temporaire et restauré ensuite).Vous n'avez généralement pas besoin d'un serveur Perforce en activité : la vulnérabilité réside dans la façon dont Composer construit la ligne de commande
p4, et la charge utile injectée s'exécute avant/autour de toute connexionp4réelle. Composer peut journaliser une erreur de connexion Perforce — c'est attendu et cela n'affecte pas la preuve par fichier marqueur.
Clonez / placez le PoC dans un répertoire de travail.
Fournissez un composer.json dans le même répertoire que le PoC. Un minimal suffit :
{
"name": "research/cve-2026-40176-poc",
"description": "Manifeste de base pour le PoC différentiel CVE-2026-40176",
"require": {}
}
Obtenez les deux binaires Composer et placez-les là où le PoC les attend (chemins par défaut indiqués) :
/usr/local/bin/composer-2.9.5.phar # affecté
/usr/local/bin/composer-2.9.6.phar # corrigé
Vous pouvez télécharger des versions spécifiques de Composer depuis l'archive officielle, par exemple :
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
Si vos chemins diffèrent, éditez les deux arguments du constructeur en bas de
CVE202640176Test.php.
php CVE202640176Test.php
Le harnais exécute les deux versions de Composer tour à tour et affiche un verdict final. Le composer.json original est restauré automatiquement même si une exécution échoue (le travail se fait dans des répertoires temporaires jetables).
Le dépôt fournit un laboratoire conteneurisé qui reproduit exactement l'environnement : un runtime PHP CLI plus les deux versions de Composer épinglées aux chemins attendus par le PoC, complètement isolé réseau à l'exécution.
docker compose run --rm poc
Cette commande construit cve-2026-40176-lab:latest (télécharge Composer 2.9.5 et 2.9.6 et vérifie chaque --version pendant la construction) et exécute le test différentiel dans un conteneur non privilégié, sans accès réseau sortant.
Ce que le laboratoire garantit :
poc s'exécute sur un réseau pont internal (pas d'accès réseau sortant hôte/Internet), avec cap_drop: ALL et no-new-privileges. La charge utile injectée reste contenue.Réépinglez les versions (doivent rester synchronisées avec les deux chemins du constructeur dans le PoC) via les arguments de construction :
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
Optionnel — serveur Perforce en direct. Un service p4d est disponible sous le profil full-lab (docker compose --profile full-lab up). La preuve par marqueur n'en a pas besoin ; il existe pour les chercheurs qui souhaitent un endpoint p4:// en direct. Notez que la charge utile du PoC cible 127.0.0.1:1666, donc un routage vers un conteneur p4d séparé nécessite de pointer l'URL du PoC vers l'hôte p4d.
Résultat honnête : contre les versions réelles et publiées de Composer
2.9.5et2.9.6, le PoC ne se déclenche pas actuellement et le laboratoire rapporteINCONCLUSIF / ÉCHEC.
L'exécution de Composer affecté (2.9.5) sur le manifeste malveillant du PoC lève, à l'intérieur de Composer, avant la construction de toute commande p4/shell :
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
PerforceDriver::initialize() lit $this->repoConfig['depot'] en premier lieu, mais l'entrée de dépôt du PoC ne fournit que type et url (pas de clé depot). Le pilote abandonne à ce stade, donc la charge utile injectée ;touch <marqueur> dans l'URL n'est jamais atteinte et aucun marqueur n'est créé. L'isolation réseau n'en est pas la cause — la même erreur se produit avec un accès réseau complet.
Ce que cela signifie :
INCONCLUSIF est une propriété de la charge utile du PoC, pas de l'environnement.depot (et réalistiquement un endpoint p4d en direct sous le profil full-lab). Affiner la charge utile jusqu'à ce point est du développement d'exploit au-delà de "mettre en place le laboratoire" et est intentionnellement laissé hors de portée ici.La "Sortie attendue" ci-dessous est le résultat intentionnel/idéalisé du PoC, conservé pour référence ; ce n'est pas ce que la charge utile actuelle produit contre le vrai pilote.
Une démonstration réussie ressemble approximativement à ceci (les chemins et IDs varieront) :
=== CVE-2026-40176 PoC démarré ===
- Version Composer 2.9.5 : 2.9.5
- Version Composer 2.9.6 : 2.9.6
Répertoire temporaire préparé : /tmp/cve20264176_5_20260610_142233
composer.json malveillant écrit dans /tmp/cve20264176_5_20260610_142233
Exécution de Composer dans /tmp/cve20264176_5_20260610_142233…
- Version Composer analysée : 2.9.5
- Marqueur /tmp/cve20264176_5_.../poc_marker_5.txt créé avec l'ID attendu.
- La sortie montre une activité du pilote Perforce.
- Code de sortie de l'exécution affectée : 1
Répertoire temporaire préparé : /tmp/cve20264176_6_20260610_142233
composer.json malveillant écrit dans /tmp/cve20264176_6_20260610_142233
Exécution de Composer dans /tmp/cve20264176_6_20260610_142233…
- Version Composer analysée : 2.9.6
✘ Fichier marqueur /tmp/cve20264176_6_.../poc_marker_6.txt introuvable.
- La sortie montre une activité du pilote Perforce.
- Code de sortie de l'exécution corrigée : 1
=== CVE-2026-40176 PoC terminé ===
=== RÉSULTAT DU TEST : RÉUSSI (affecté réussi, corrigé échoué) ===
Un code de sortie non nul de Composer est normal — composer update échoue finalement à récupérer le paquet (fictif). La preuve est le fichier marqueur, pas le statut de sortie de Composer.
| Résultat | Signification |
|---|---|
| RÉUSSI (affecté réussi, corrigé échoué) | Confirmé : 2.9.5 a exécuté la commande injectée, 2.9.6 non. La vulnérabilité et son correctif sont tous deux reproduits. |
| INCONCLUSIF / ÉCHEC | Une ou plusieurs vérifications ne concordent pas. Inspectez les lignes ✓/✗ par exécution : mauvais chemin de binaire, décalage de version, marqueur affecté manquant (différence d'environnement/échappement), ou l'exécution corrigée créant inopinément un marqueur. |
Causes courantes d'un résultat inconclusif :
PerforceDriver.php:40 avec Undefined array key "depot" — la configuration de dépôt manque d'une clé depot, donc Composer n'atteint jamais le chemin de construction de la commande p4. C'est ce qui se produit avec la charge utile actuelle du PoC contre les vrais 2.9.5/2.9.6 (voir Statut de reproduction).2.9.5 / 2.9.6.exec() de PHP est sandboxé/désactivé.p4 (chemin du pilote non atteint).CVE2026-40176/
├── CVE202640176Test.php # Le PoC : classe CVE202640176Test + point d'entrée
├── composer.json # Manifeste de base que le PoC lit/restaure à l'exécution
├── Dockerfile # Image du laboratoire : PHP CLI + Composer 2.9.5 & 2.9.6 épinglés
├── docker-compose.yml # Exécuteur `poc` (+ `p4d` optionnel sous profil full-lab)
├── .dockerignore # Réduit le contexte de construction
├── README.md # Ce fichier
└── .gitignore # Exclut l'état local des agents/outils
Le PoC entier tient dans un fichier :
__construct() — stocke les deux chemins de binaires, génère un ID d'exécution horodaté, prend un instantané du composer.json original.run() — orchestre la vérification préalable, l'exécution affectée, l'exécution corrigée, la restauration, et le verdict.prepareTempDir() — crée un répertoire de travail isolé par exécution.writeComposerJson() — construit le manifeste malveillant avec l'URL p4:// injectée.runComposer() — exécute composer update (arguments échappés avec escapeshellarg()) et capture la sortie + le code de sortie.validateRun() — vérifie la version, le fichier marqueur, et l'activité du pilote Perforce.preflightVersion() — lit --version depuis un binaire donné.composer.json original est restauré dans un bloc finally quel que soit le résultat.escapeshellarg() (afin que le PoC n'injecte pas accidentellement dans ses propres appels exec()). La vulnérabilité se trouve une couche plus profond — dans la manière dont Composer lui-même construit la commande p4 — ce que cible exactement la charge utile.touch/echo dans un marqueur temporaire unique, rendant le PoC sûr à exécuter de manière répétée sans effets secondaires sur l'hôte.exec("cd … && php …") et la charge utile ;/&& supposent un shell de type Unix ; Windows n'est pas pris en charge en l'état.mkdir / permissions. Les répertoires temporaires sont créés avec le mode 0777 ; resserrez les droits si vous exécutez dans un environnement partagé.Composer X.Y.Z dans la sortie ; des bannières Composer inhabituelles pourraient faire échouer la correspondance.Ce dépôt existe pour comprendre et se défendre contre CVE-2026-40176.
composer install/update sur des fichiers composer.json non fiables (par exemple, dans des pipelines CI qui traitent le code source de projets tiers) sans isolation.2.9.5 / 2.9.6 spécifiques)2.9.6 (consultez le suivi de sécurité de votre distribution / la GitHub Advisory Database pour les détails du correctif faisant autorité)Auteur : Ikarolaborda · PoC daté du 2026-06-10.