
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 :