
Prova di concetto differenziale per CVE-2026-40176, che dimostra l'iniezione di comandi del sistema operativo nel driver Perforce di Composer tramite un URL di repository malintenzionato, con test A/B automatizzati sulle versioni affette e corrette.
Una proof of concept autonoma, in stile OOP PHP, che dimostra e verifica in modo differenziale una vulnerabilità di iniezione di comandi nel driver del repository Perforce di Composer.
La PoC esegue lo stesso composer.json malevolo contro due binari di Composer — una versione affetta (2.9.5) e una versione corretta (2.9.6) — e dimostra il bug osservando un effetto collaterale (un file marcatore scritto da un comando shell iniettato) che si attiva sulla versione affetta ma non su quella corretta.
⚠️ Solo per ricerca sulla sicurezza autorizzata e test difensivi. Vedi Uso Responsabile.
| CVE | CVE-2026-40176 |
| Componente | Composer — driver repository/VCS Perforce (perforce) |
| Classe | Iniezione di comandi del sistema operativo tramite URL di repository controllato dall'attaccante |
| Superficie d'attacco | Un composer.json contenente una voce repositories manipolata di tipo perforce |
| Affetto | Composer 2.9.5 |
| Corretto | Composer 2.9.6 |
| Attivazione | Risoluzione/aggiornamento delle dipendenze (composer update) contro il manifest malevolo |
| Impatto | Esecuzione arbitraria di comandi sulla macchina che esegue Composer |
| Linguaggio PoC | PHP (file singolo, senza dipendenze esterne) |
Composer può risolvere pacchetti da diversi sistemi di controllo versione. Per Perforce, il repository è identificato da un URL p4:// che codifica host, porta e utente/stream. Quando il driver Perforce di Composer costruisce la riga di comando p4 sottostante, i campi presi dall'URL controllato dall'attaccante non sono sufficientemente sanificati prima di essere passati a una shell.
Poiché l'autore del manifest controlla completamente l'URL del repository, un attaccante che riesca a far eseguire a una vittima composer update/composer install contro un composer.json malevolo (ad esempio, una dipendenza avvelenata, un repository ostile o un job CI che elabora file di progetto non fidati) può uscire dall'invocazione p4 prevista ed eseguire comandi OS arbitrari con i privilegi del processo Composer.
Questo appartiene alla stessa famiglia di storici problemi di iniezione di argomenti nei driver VCS di Composer, dove i valori di URL/branch/stream finiscono in comandi shell senza escaping. Composer 2.9.6 rafforza il driver Perforce in modo che il payload iniettato non venga più eseguito.
La descrizione autorevole del comportamento qui dimostrato è il codice sorgente della PoC stesso (
CVE202640176Test.php); consultare l'avviso ufficiale e il changelog di Composer per i dettagli della correzione a monte.
La PoC è una singola classe, CVE202640176Test, che esegue un esperimento controllato A/B (differenziale):
--version su entrambi i binari di Composer, quello affetto (2.9.5) e quello corretto (2.9.6), e si interrompe anticipatamente se uno dei due non può essere invocato.composer.json la cui sezione repositories contiene una voce perforce con un URL p4:// malevolo che trasporta un payload shell iniettato.composer update in quella directory.finally ripristina sempre il composer.json originale nella directory del progetto.PASS solo quando l'esecuzione affetta mostra l'effetto collaterale e l'esecuzione corretta no.Per ogni esecuzione, validateRun() controlla tre cose:
| Controllo | Cosa prova |
|---|---|
| Il file marcatore esiste e contiene l'ID di esecuzione | Il payload iniettato touch/echo è effettivamente eseguito — cioè l'iniezione di comandi è riuscita. |
L'output di Composer menziona p4 | Il percorso del codice del driver Perforce è stato raggiunto (il payload è stato elaborato dal componente giusto, non da qualche passaggio non correlato). |
| Versione di Composer analizzata == attesa | Il binario corretto (2.9.5 vs 2.9.6) è stato quello eseguito. |
Un'esecuzione è "OK" solo quando tutti e tre passano. Il test complessivo passa quando l'esecuzione affetta è OK e quella corretta non lo è — la firma precisa di una vulnerabilità reale che è stata successivamente patchata.
L'URL del repository malevolo è costruito in writeComposerJson():
p4://127.0.0.1:1666:attacker_user;touch <marcatore> && echo '<runId>' > <marcatore>:client_test
Analizzandolo:
p4://127.0.0.1:1666:attacker_user — un URL Perforce dall'aspetto ben formato (host, porta 1666, utente).;touch <marcatore> && echo '<runId>' > <marcatore> — i comandi shell iniettati. Il ; iniziale termina il comando p4 previsto; touch crea il file marcatore, e echo '<runId>' > <marcatore> scrive l'ID di esecuzione univoco nel file in modo che la PoC possa confermare che il payload (e non un processo non correlato) ha prodotto il file.:client_test — testo finale per mantenere plausibile il resto dell'analisi dell'URL.Sul driver affetto i metacaratteri della shell sono onorati e il file marcatore viene creato. Sul driver corretto il valore è opportunamente escaped/citato, quindi la stessa stringa viene trattata come dati inerti e nessun marcatore appare.
Nota: la PoC utilizza un ID di esecuzione univoco con timestamp e scrive il suo marcatore all'interno di una directory temporanea isolata, quindi il payload è benigno e si autopulisce piuttosto che essere distruttivo.
2.9.5 (affetto)2.9.6 (corretto)exec() esegue cd … && php …). Progettato per Linux/macOS.composer.json base nella directory del progetto (viene letto all'avvio, copiato in ogni esecuzione temporanea e ripristinato dopo).Generalmente non è necessario un server Perforce attivo: la vulnerabilità risiede nel modo in cui Composer costruisce la riga di comando
p4, e il payload iniettato viene eseguito prima/intorno a qualsiasi connessionep4reale. Composer potrebbe registrare un errore di connessione Perforce — ciò è previsto e non influisce sulla prova del file marcatore.
Clonare / posizionare la PoC in una directory di lavoro.
Fornire un composer.json nella stessa directory della PoC. Un minimo è sufficiente:
{
"name": "research/cve-2026-40176-poc",
"description": "Manifest base per la PoC differenziale CVE-2026-40176",
"require": {}
}
Ottenere i due binari Composer e posizionarli dove la PoC li aspetta (valori predefiniti mostrati):
/usr/local/bin/composer-2.9.5.phar # affetto
/usr/local/bin/composer-2.9.6.phar # corretto
È possibile scaricare versioni specifiche di Composer dall'archivio ufficiale, ad esempio:
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
Se i percorsi differiscono, modificare i due argomenti del costruttore in fondo a
CVE202640176Test.php.
php CVE202640176Test.php
L'harness esegue entrambe le versioni di Composer a turno e stampa un verdetto finale. Il composer.json originale viene ripristinato automaticamente anche se un'esecuzione fallisce (il lavoro avviene in directory temporanee usa-e-getta).
Il repository include un laboratorio containerizzato che replica esattamente l'ambiente: un runtime PHP CLI più le due versioni di Composer fissate ai percorsi che la PoC si aspetta, completamente isolate in rete in fase di esecuzione.
docker compose run --rm poc
Questo costruisce cve-2026-40176-lab:latest (scaricando Composer 2.9.5 e 2.9.6 e verificando ogni --version durante la build) e esegue il test differenziale all'interno di un contenitore non privilegiato e senza uscita in rete.
Cosa garantisce il laboratorio:
poc viene eseguito su una rete bridge internal (nessuna uscita verso host/internet), con cap_drop: ALL e no-new-privileges. Il payload di iniezione rimane contenuto.Ripuntare le versioni (devono rimanere sincronizzate con i due percorsi del costruttore nella PoC) tramite build args:
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
Opzionale — server Perforce live. Un servizio p4d è disponibile sotto il profilo full-lab (docker compose --profile full-lab up). La prova basata su marker non ne ha bisogno; esiste per ricercatori che desiderano un endpoint p4:// live. Nota che il payload della PoC punta a 127.0.0.1:1666, quindi il routing attraverso un contenitore p4d separato richiede di puntare l'URL della PoC all'host p4d.
Risultato onesto: contro le versioni reali, pubblicate di Composer
2.9.5e2.9.6, la PoC attualmente non si attiva, e il laboratorio riportaINCONCLUSIVO / FALLITO.
Eseguire il Composer affetto (2.9.5) contro il manifest malevolo della PoC genera, all'interno di Composer, prima che qualsiasi comando p4/shell venga costruito:
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
PerforceDriver::initialize() legge $this->repoConfig['depot'] per prima cosa, ma la voce del repository della PoC fornisce solo type e url (nessuna chiave depot). Il driver si interrompe a quel punto, quindi il payload iniettato ;touch <marcatore> nell'URL non viene mai raggiunto e nessun marcatore viene creato. L'isolamento di rete non è la causa — lo stesso errore si verifica con uscita completa in rete.
Cosa significa:
INCONCLUSIVO è una proprietà del payload della PoC, non dell'ambiente.depot (e realisticamente di un endpoint p4d live sotto il profilo full-lab). Raffinare il payload fino a quel punto è sviluppo di exploit oltre "allestire il laboratorio" ed è intenzionalmente lasciato fuori ambito qui.L'"Output Previsto" di seguito è il risultato inteso/idealizzato della PoC, mantenuto per riferimento; non è ciò che il payload attuale produce contro il driver reale.
Una dimostrazione riuscita assomiglia approssimativamente a questo (percorsi e ID varieranno):
=== CVE-2026-40176 PoC avviata ===
- Versione Composer 2.9.5: 2.9.5
- Versione Composer 2.9.6: 2.9.6
Preparata dir temp: /tmp/cve20264176_5_20260610_142233
Scritto composer.json malevolo in /tmp/cve20264176_5_20260610_142233
Esecuzione di Composer in /tmp/cve20264176_5_20260610_142233…
- Versione Composer analizzata: 2.9.5
- Marcatore /tmp/cve20264176_5_.../poc_marker_5.txt creato con ID previsto.
- L'output mostra attività del driver Perforce.
- Codice di uscita esecuzione affetta: 1
Preparata dir temp: /tmp/cve20264176_6_20260610_142233
Scritto composer.json malevolo in /tmp/cve20264176_6_20260610_142233
Esecuzione di Composer in /tmp/cve20264176_6_20260610_142233…
- Versione Composer analizzata: 2.9.6
✘ File marcatore /tmp/cve20264176_6_.../poc_marker_6.txt non trovato.
- L'output mostra attività del driver Perforce.
- Codice di uscita esecuzione corretta: 1
=== CVE-2026-40176 PoC terminata ===
=== RISULTATO TEST: PASS (affetta riuscita, corretta fallita) ===
Un codice di uscita di Composer diverso da zero è normale — composer update alla fine non riesce a recuperare il pacchetto (fittizio). La prova è il file marcatore, non lo stato di uscita di Composer.
| Risultato | Significato |
|---|---|
| PASS (affetta riuscita, corretta fallita) | Confermato: 2.9.5 ha eseguito il comando iniettato, 2.9.6 no. La vulnerabilità e la sua correzione sono entrambe riprodotte. |
| INCONCLUSIVO / FALLITO | Uno o più controlli non hanno corrisposto. Ispezionare le righe ✓/✗ per esecuzione: percorso binario errato, disallineamento versione, marcatore affetto mancante (differenza di ambiente/escaping), o l'esecuzione corretta che crea inaspettatamente un marcatore. |
Cause comuni di un risultato inconclusivo:
PerforceDriver.php:40 con Undefined array key "depot" — la configurazione del repository manca di una chiave depot, quindi Composer non raggiunge mai il percorso di costruzione del comando p4. Questo è ciò che accade con il payload attuale della PoC contro la versione reale 2.9.5/2.9.6 (vedi Stato di Riproduzione).2.9.5 / 2.9.6.exec() è sandboxed/disabilitata.p4 (percorso del driver non raggiunto).CVE2026-40176/
├── CVE202640176Test.php # La PoC: classe CVE202640176Test + punto di ingresso
├── composer.json # Manifest base che la PoC legge/ripristina in fase di esecuzione
├── Dockerfile # Immagine del laboratorio: PHP CLI + Composer 2.9.5 e 2.9.6 fissati
├── docker-compose.yml # Runner `poc` (+ `p4d` opzionale sotto profilo full-lab)
├── .dockerignore # Riduce il contesto di build
├── README.md # Questo file
└── .gitignore # Esclude lo stato locale di agent/tooling
L'intera PoC è un file:
__construct() — memorizza i due percorsi binari, crea un ID di esecuzione con timestamp, istantanea il composer.json originale.run() — orchestra il preflight, l'esecuzione affetta, l'esecuzione corretta, il ripristino e il verdetto.prepareTempDir() — crea una directory di lavoro isolata per ogni esecuzione.writeComposerJson() — costruisce il manifest malevolo con l'URL p4:// iniettato.runComposer() — esegue composer update (argomenti escaped con escapeshellarg()) e cattura output + codice di uscita.validateRun() — controlla versione, file marcatore e attività del driver Perforce.preflightVersion() — legge --version da un dato binario.composer.json originale viene ripristinato in un blocco finally indipendentemente dal risultato.escapeshellarg() (così la PoC non si inietta accidentalmente nelle proprie chiamate exec()). La vulnerabilità vive un livello più in profondità — in come Composer stesso costruisce il comando p4 — che è esattamente ciò che il payload prende di mira.touch/echo in un marcatore temporaneo univoco, rendendo la PoC sicura da eseguire ripetutamente senza effetti collaterali sull'host.exec("cd … && php …") e il payload ;/&& presuppongono una shell simile a Unix; Windows non è supportato così com'è.mkdir. Le directory temporanee sono create con modalità 0777; stringere se eseguite in un ambiente condiviso.Composer X.Y.Z dall'output; banner di Composer insoliti potrebbero impedire la corrispondenza.Questo repository esiste per comprendere e difendersi da CVE-2026-40176.
composer install/update su file composer.json non fidati (ad esempio, in pipeline CI che elaborano codice sorgente di terze parti) senza isolamento.2.9.5 / 2.9.6)2.9.6 (consultare il tracker di sicurezza della propria distribuzione / il Database Advisory di GitHub per i dettagli della correzione autorevole)Autore: Ikarolaborda · PoC datata 2026-06-10.