
Prova de conceito diferencial para CVE-2026-40176, demonstrando OS command injection no driver Perforce do Composer através de uma URL de repositório maliciosa, com teste A/B automatizado contra versões afetadas e corrigidas.
Uma prova de conceito autossuficiente, em estilo OOP em PHP, que demonstra e verifica diferencialmente uma vulnerabilidade de injeção de comandos no driver de repositório Perforce do Composer.
O PoC executa o mesmo composer.json malicioso contra duas versões do Composer — uma afetada (2.9.5) e uma corrigida (2.9.6) — e comprova a falha observando um efeito colateral (um arquivo marcador escrito por um comando shell injetado) que ocorre na versão afetada, mas não na corrigida.
⚠️ Apenas para pesquisa de segurança autorizada e testes defensivos. Consulte Uso Responsável.
O Composer pode resolver pacotes de vários sistemas de controle de versão. Para o Perforce, o repositório é identificado por uma URL p4:// que codifica o host, porta e usuário/stream. Quando o driver Perforce do Composer constrói a linha de comando p4 subjacente, campos retirados da URL controlada pelo atacante não são suficientemente sanitizados antes de serem passados para um shell.
Como o autor do manifesto controla completamente a URL do repositório, um atacante que consiga fazer uma vítima executar composer update/composer install contra um composer.json malicioso (por exemplo, uma dependência envenenada, um repositório hostil ou um job de CI processando arquivos de projeto não confiáveis) pode escapar da invocação pretendida do p4 e executar comandos arbitrários do sistema operacional com os privilégios do processo Composer.
Isso pertence à mesma família de problemas históricos de injeção de argumentos em drivers VCS do Composer, onde valores de URL/branch/stream fluem para comandos shell sem escape. O Composer 2.9.6 endurece o driver Perforce para que o payload injetado não seja mais executado.
A descrição autoritativa do comportamento demonstrado aqui é o próprio código-fonte do PoC (
CVE202640176Test.php); consulte o aviso oficial e o changelog do Composer para obter detalhes da correção upstream.
O PoC é uma única classe, CVE202640176Test, que realiza um experimento controlado A/B (diferencial) :
--version em ambas as versões do Composer (a afetada 2.9.5 e a corrigida 2.9.6) e aborta cedo se alguma não puder ser invocada.composer.json cuja seção repositories contém uma entrada perforce com uma URL p4:// maliciosa carregando um payload shell injetado.composer update nesse diretório.finally sempre restaura o composer.json original no diretório do projeto.PASS apenas quando a execução afetada mostra o efeito colateral a execução corrigida não.Para cada execução, validateRun() verifica três coisas:
Uma execução é "OK" apenas quando todos os três passam. O teste geral passa quando a execução afetada é OK e a executada corrigida não é — a assinatura precisa de uma vulnerabilidade real que foi subsequentemente corrigida.
A URL maliciosa do repositório é construída em writeComposerJson():
p4://127.0.0.1:1666:attacker_user;touch <marcador> && echo '<runId>' > <marcador>:client_test
Detalhando:
p4://127.0.0.1:1666:attacker_user — uma URL Perforce com aparência bem formada (host, porta 1666, usuário).;touch <marcador> && echo '<runId>' > <marcador> — os comandos shell injetados. O ; inicial termina o comando p4 pretendido; touch cria o arquivo marcador, e echo '<runId>' > <marcador> escreve o ID único da execução nele para que o PoC possa confirmar que o payload (e não algum outro processo) produziu o arquivo.:client_test — texto adicional para manter o restante da análise da URL plausível.No driver afetado, os meta-caracteres do shell são respeitados e o arquivo marcador é criado. No driver corrigido, o valor é devidamente escapado/entre aspas, então a mesma string é tratada como dados inertes e nenhum marcador aparece.
Nota: o PoC usa um ID de execução único com timestamp e escreve seu marcador dentro de um diretório temporário isolado, portanto o payload é benigno e autolimpante, em vez de destrutivo.
2.9.5 (afetado)2.9.6 (corrigido)exec() executa cd … && php …). Projetado para Linux/macOS.composer.json base no diretório do projeto (ele é lido na inicialização, copiado para cada execução temporária e restaurado depois).Geralmente você não precisa de um servidor Perforce ativo: a vulnerabilidade está em como o Composer constrói a linha de comando
p4, e o payload injetado é executado antes/em torno de qualquer conexãop4real. O Composer pode registrar um erro de conexão Perforce — isso é esperado e não afeta a prova do arquivo marcador.
Clone / coloque o PoC em um diretório de trabalho.
Forneça um composer.json no mesmo diretório do PoC. Um mínimo é suficiente:
{
"name": "research/cve-2026-40176-poc",
"description": "Manifesto base para o PoC diferencial CVE-2026-40176",
"require": {}
}
Obtenha os dois binários do Composer e coloque-os onde o PoC espera (padrões mostrados):
/usr/local/bin/composer-2.9.5.phar # afetado
/usr/local/bin/composer-2.9.6.phar # corrigido
Você pode baixar versões específicas do Composer do arquivo oficial, por exemplo:
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 seus caminhos forem diferentes, edite os dois argumentos do construtor no final de
CVE202640176Test.php.
php CVE202640176Test.php
O harness executa ambas as versões do Composer em sequência e imprime um veredito final. O composer.json original é restaurado automaticamente mesmo se uma execução falhar (o trabalho acontece em diretórios temporários descartáveis).
O repositório inclui um laboratório containerizado que replica o ambiente exatamente: um runtime PHP CLI mais as duas versões fixadas do Composer nos caminhos que o PoC espera, totalmente isolado de rede em tempo de execução.
docker compose run --rm poc
Isso constrói cve-2026-40176-lab:latest (baixando Composer 2.9.5 e 2.9.6 e verificando cada --version durante a construção) e executa o teste diferencial dentro de um container sem privilégios e sem egresso.
O que o laboratório garante:
poc é executado em uma rede bridge internal (sem egresso para host/internet), com cap_drop: ALL e no-new-privileges. O payload de injeção permanece contido.Refixe as versões (devem permanecer sincronizadas com os dois caminhos do construtor no PoC) via argumentos de construção:
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
Opcional — servidor Perforce ativo. Um serviço p4d está disponível sob o perfil full-lab (docker compose --profile full-lab up). A prova baseada em marcador não precisa dele; ele existe para pesquisadores que desejam um endpoint p4:// ao vivo. Observe que o payload do PoC tem como alvo 127.0.0.1:1666, portanto o roteamento através de um container p4d separado requer apontar a URL do PoC para o host p4d.
Resultado honesto: contra as versões reais e publicadas do Composer
2.9.5e2.9.6, o PoC não é acionado atualmente, e o laboratório relataINCONCLUSIVE / FAIL.
Executar o Composer afetado (2.9.5) contra o manifesto malicioso do PoC gera, dentro do Composer, antes que qualquer comando p4/shell seja construído:
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
PerforceDriver::initialize() lê $this->repoConfig['depot'] logo de início, mas a entrada de repositório do PoC fornece apenas type e url (sem chave depot). O driver aborta nesse ponto, então o payload ;touch <marcador> injetado na URL nunca é alcançado e nenhum marcador é criado. O isolamento de rede não é a causa — o mesmo erro ocorre com egresso completo.
O que isso significa:
INCONCLUSIVE é uma propriedade do payload do PoC, não do ambiente.depot (e realisticamente um endpoint p4d ativo sob o perfil full-lab). Refinar o payload até esse ponto é desenvolvimento de exploração além de "montar o laboratório" e está intencionalmente fora do escopo aqui.A "Saída Esperada" abaixo é o resultado pretendido/idealizado do PoC, mantido para referência; não é o que o payload atual produz contra o driver real.
Uma demonstração bem-sucedida parece aproximadamente assim (caminhos e IDs variarão):
=== CVE-2026-40176 PoC iniciado ===
- Versão do Composer 2.9.5: 2.9.5
- Versão do Composer 2.9.6: 2.9.6
Diretório temporário preparado: /tmp/cve20264176_5_20260610_142233
composer.json malicioso escrito em /tmp/cve20264176_5_20260610_142233
Executando Composer em /tmp/cve20264176_5_20260610_142233…
- Versão do Composer analisada: 2.9.5
- Marcador /tmp/cve20264176_5_.../poc_marker_5.txt criado com ID esperado.
- Saída mostra atividade do driver Perforce.
- Código de saída da execução afetada: 1
Diretório temporário preparado: /tmp/cve20264176_6_20260610_142233
composer.json malicioso escrito em /tmp/cve20264176_6_20260610_142233
Executando Composer em /tmp/cve20264176_6_20260610_142233…
- Versão do Composer analisada: 2.9.6
✘ Arquivo marcador /tmp/cve20264176_6_.../poc_marker_6.txt não encontrado.
- Saída mostra atividade do driver Perforce.
- Código de saída da execução corrigida: 1
=== CVE-2026-40176 PoC finalizado ===
=== RESULTADO DO TESTE: PASS (afetado bem-sucedido, corrigido falhou) ===
Um código de saída diferente de zero do Composer é normal — composer update eventualmente falha ao buscar o pacote (falso). A prova é o arquivo marcador, não o status de saída do Composer.
| Resultado | Significado |
|---|---|
| PASS (afetado bem-sucedido, corrigido falhou) | Confirmado: 2.9.5 executou o comando injetado, 2.9.6 não. A vulnerabilidade e sua correção foram ambas reproduzidas. |
| INCONCLUSIVE / FAIL | Uma ou mais verificações não se alinharam. Inspecione as linhas ✓/✗ por execução: caminho binário errado, incompatibilidade de versão, marcador afetado ausente (diferença de ambiente/escape), ou a execução corrigida inesperadamente criando um marcador. |
Causas comuns de um resultado inconclusivo:
PerforceDriver.php:40 com Undefined array key "depot" — a configuração do repositório não possui uma chave depot, então o Composer nunca atinge o caminho de construção do comando p4. Isso é o que acontece com o payload atual do PoC contra o 2.9.5/2.9.6 real (consulte Status de Reprodução).2.9.5 / 2.9.6.exec() do PHP está em sandbox/desabilitado.p4 (caminho do driver não alcançado).CVE2026-40176/
├── CVE202640176Test.php # O PoC: classe CVE202640176Test + ponto de entrada
├── composer.json # Manifesto base que o PoC lê/restaura em tempo de execução
├── Dockerfile # Imagem do laboratório: PHP CLI + Composer 2.9.5 & 2.9.6 fixados
├── docker-compose.yml # Runner `poc` (+ `p4d` opcional sob perfil full-lab)
├── .dockerignore # Apara o contexto de construção
├── README.md # Este arquivo
└── .gitignore # Exclui estado local de agente/ferramentas
O PoC inteiro é um arquivo:
__construct() — armazena os dois caminhos dos binários, gera um ID de execução com timestamp, tira um snapshot do composer.json original.run() — orquestra a pré-verificação, a execução afetada, a execução corrigida, a restauração e o veredito.prepareTempDir() — cria um diretório de trabalho isolado por execução.writeComposerJson() — constrói o manifesto malicioso com a URL p4:// injetada.runComposer() — executa composer update (argumentos escapados com escapeshellarg()) e captura saída + código de saída.validateRun() — verifica versão, arquivo marcador e atividade do driver Perforce.preflightVersion() — lê --version de um determinado binário.composer.json original é restaurado em um bloco finally independentemente do resultado.escapeshellarg() (para que o PoC não injete acidentalmente em suas próprias chamadas exec()). A vulnerabilidade vive uma camada mais profunda — em como o próprio Composer constrói o comando p4 — que é exatamente o alvo do payload.touch/echo em um marcador temporário único, tornando o PoC seguro para ser executado repetidamente sem efeitos colaterais no host.exec("cd … && php …") e o payload ;/&& assumem um shell tipo Unix; Windows não é suportado como está.mkdir. Diretórios temporários são criados com modo 0777; aperte se estiver executando em um ambiente compartilhado.Composer X.Y.Z da saída; banners incomuns do Composer podem derrotar a correspondência.Este repositório existe para entender e defender contra CVE-2026-40176.
composer install/update em arquivos composer.json não confiáveis (por exemplo, em pipelines de CI que processam código-fonte de terceiros) sem isolamento.2.9.5 / 2.9.6)2.9.6 (consulte o rastreador de segurança da sua distribuição / o GitHub Advisory Database para obter detalhes autoritativos da correção)Autor: Ikarolaborda · PoC datado de 2026-06-10.
| CVE | CVE-2026-40176 |
| Componente | Composer — driver de repositório/VCS Perforce (perforce) |
| Classe | Injeção de comandos do sistema operacional via URL de repositório controlada pelo atacante |
| Superfície de ataque | Um composer.json contendo uma entrada repositories manipulada com type: perforce |
| Afetada | Composer 2.9.5 |
| Corrigida | Composer 2.9.6 |
| Gatilho | Resolução/atualização de dependências (composer update) contra o manifesto malicioso |
| Impacto | Execução arbitrária de comandos na máquina que executa o Composer |
| Linguagem do PoC | PHP (arquivo único, sem dependências externas) |
| Verificação | O que comprova |
|---|
| Arquivo marcador existe e contém o ID da execução | O payload touch/echo injetado realmente foi executado — ou seja, a injeção de comando foi bem-sucedida. |
A saída do Composer menciona p4 | O caminho do código do driver Perforce foi alcançado (o payload foi processado pelo componente correto, não por alguma etapa não relacionada). |
| Versão do Composer analisada == esperada | O binário correto (2.9.5 vs 2.9.6) foi o que foi executado. |