
Prueba de concepto diferencial para CVE-2026-40176, que demuestra inyección de comandos del sistema operativo en el controlador Perforce de Composer a través de una URL de repositorio maliciosa, con pruebas A/B automatizadas contra versiones afectadas y corregidas.
Una prueba de concepto autocontenida, con estilo OOP en PHP, que demuestra y verifica diferencialmente una vulnerabilidad de inyección de comandos en el controlador de repositorio Perforce de Composer.
La PoC ejecuta el mismo composer.json malicioso contra dos binarios de Composer: una versión afectada (2.9.5) y una versión corregida (2.9.6), y demuestra el error observando un efecto secundario (un archivo marcador escrito por un comando de shell inyectado) que se activa en la versión afectada pero no en la corregida.
⚠️ Solo para investigación de seguridad autorizada y pruebas defensivas. Consulte Uso responsable.
| CVE | CVE-2026-40176 |
| Componente | Composer — controlador de repositorio/VCS Perforce (perforce) |
| Clase | Inyección de comandos del sistema operativo a través de URL de repositorio controlada por el atacante |
| Superficie de ataque | Un composer.json que contiene una entrada repositories manipulada con type: perforce |
| Afectado | Composer 2.9.5 |
| Corregido | Composer 2.9.6 |
| Disparador | Resolución/actualización de dependencias (composer update) contra el manifiesto malicioso |
| Impacto | Ejecución arbitraria de comandos en la máquina que ejecuta Composer |
| Lenguaje de la PoC | PHP (archivo único, sin dependencias externas) |
Composer puede resolver paquetes de varios sistemas de control de versiones. Para Perforce, el repositorio se identifica mediante una URL p4:// que codifica el host, puerto y usuario/stream. Cuando el controlador Perforce de Composer construye la línea de comandos subyacente de p4, los campos tomados de la URL controlada por el atacante no se sanitizan suficientemente antes de pasarse a un shell.
Debido a que el autor del manifiesto controla completamente la URL del repositorio, un atacante que pueda lograr que una víctima ejecute composer update/composer install contra un composer.json malicioso (por ejemplo, una dependencia envenenada, un repositorio hostil o un trabajo de CI que procese archivos de proyecto no confiables) puede escapar de la invocación prevista de p4 y ejecutar comandos arbitrarios del sistema operativo con los privilegios del proceso de Composer.
Esto pertenece a la misma familia que los problemas históricos de inyección de argumentos en los controladores VCS de Composer, donde los valores de URL/rama/stream fluyen hacia comandos de shell sin escape. Composer 2.9.6 refuerza el controlador Perforce para que el payload inyectado ya no se ejecute.
La descripción autorizada del comportamiento demostrado aquí es el código fuente de la PoC (
CVE202640176Test.php); consulte el aviso oficial y el registro de cambios de Composer para obtener detalles sobre la corrección upstream.
La PoC es una única clase, CVE202640176Test, que realiza un experimento controlado A/B (diferencial):
--version tanto en el binario afectado (2.9.5) como en el corregido (2.9.6) de Composer y aborta temprano si alguno no puede invocarse.composer.json cuya sección repositories contiene una entrada perforce con una URL p4:// maliciosa que lleva un payload de shell inyectado.composer update en ese directorio.finally siempre restaura el composer.json original en el directorio del proyecto.PASS solo cuando la ejecución afectada muestra el efecto secundario y la ejecución corregida no.Para cada ejecución, validateRun() verifica tres cosas:
| Verificación | Qué demuestra |
|---|---|
| Existe el archivo marcador y contiene el ID de ejecución | El payload inyectado touch/echo realmente se ejecutó — es decir, la inyección de comandos tuvo éxito. |
La salida de Composer menciona p4 | Se alcanzó la ruta de código del controlador Perforce (el payload fue procesado por el componente correcto, no por algún paso no relacionado). |
| La versión de Composer analizada coincide con la esperada | El binario correcto (2.9.5 vs 2.9.6) fue el que se ejecutó. |
Una ejecución es "OK" solo cuando las tres pasan. La prueba general pasa cuando la ejecución afectada es OK y la ejecución corregida no — la firma precisa de una vulnerabilidad real que fue parcheada posteriormente.
La URL del repositorio malicioso se construye en writeComposerJson():
p4://127.0.0.1:1666:attacker_user;touch <marcador> && echo '<idEjecucion>' > <marcador>:client_test
Desglosándola:
p4://127.0.0.1:1666:attacker_user — una URL de Perforce de aspecto bien formado (host, puerto 1666, usuario).;touch <marcador> && echo '<idEjecucion>' > <marcador> — los comandos de shell inyectados. El ; inicial termina el comando p4 previsto; touch crea el archivo marcador, y echo '<idEjecucion>' > <marcador> escribe el ID de ejecución único en él para que la PoC pueda confirmar que el payload (y no algún proceso no relacionado) produjo el archivo.:client_test — texto final para mantener el resto del análisis de la URL plausible.En el controlador afectado, los metacaracteres del shell se respetan y se crea el archivo marcador. En el controlador corregido, el valor se escapa/entrecomilla adecuadamente, por lo que la misma cadena se trata como datos inertes y no aparece ningún marcador.
Nota: la PoC utiliza un ID de ejecución único con marca de tiempo y escribe su marcador dentro de un directorio temporal aislado, por lo que el payload es benigno y se autolimpia en lugar de ser destructivo.
2.9.5 (afectado)2.9.6 (corregido)exec() ejecuta cd … && php …). Diseñado para Linux/macOS.composer.json base en el directorio del proyecto (se lee al inicio, se copia en cada ejecución temporal y se restaura después).Generalmente no necesita un servidor Perforce en funcionamiento: la vulnerabilidad está en cómo Composer construye la línea de comandos de
p4, y el payload inyectado se ejecuta antes/alrededor de cualquier conexión real ap4. Composer puede registrar un error de conexión de Perforce — eso es esperado y no afecta la prueba del archivo marcador.
Clone / coloque la PoC en un directorio de trabajo.
Proporcione un composer.json en el mismo directorio que la PoC. Uno mínimo es suficiente:
{
"name": "research/cve-2026-40176-poc",
"description": "Manifiesto base para la PoC diferencial de CVE-2026-40176",
"require": {}
}
Obtenga los dos binarios de Composer y colóquelos donde la PoC los espera (valores predeterminados mostrados):
/usr/local/bin/composer-2.9.5.phar # afectado
/usr/local/bin/composer-2.9.6.phar # corregido
Puede descargar versiones específicas de Composer desde el archivo oficial, por ejemplo:
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 sus rutas son diferentes, edite los dos argumentos del constructor al final de
CVE202640176Test.php.
php CVE202640176Test.php
El arnés ejecuta ambas versiones de Composer por turnos e imprime un veredicto final. El composer.json original se restaura automáticamente incluso si una ejecución falla (el trabajo se realiza en directorios temporales desechables).
El repositorio incluye un laboratorio contenerizado que replica el entorno exactamente: un runtime PHP CLI más las dos versiones fijadas de Composer en las rutas que la PoC espera, completamente aislado de la red en tiempo de ejecución.
docker compose run --rm poc
Esto construye cve-2026-40176-lab:latest (descargando Composer 2.9.5 y 2.9.6 y verificando cada --version durante la construcción) y ejecuta la prueba diferencial dentro de un contenedor no privilegiado y sin salida de red.
Qué garantiza el laboratorio:
poc se ejecuta en una red puente internal (sin salida al host/internet), con cap_drop: ALL y no-new-privileges. El payload de inyección permanece contenido.Puede cambiar las versiones (deben estar sincronizadas con las dos rutas del constructor en la PoC) mediante argumentos de construcción:
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
Opcional — servidor Perforce en vivo. Hay un servicio p4d disponible bajo el perfil full-lab (docker compose --profile full-lab up). La prueba basada en marcadores no lo necesita; existe para investigadores que deseen un endpoint p4:// en vivo. Tenga en cuenta que el payload de la PoC apunta a 127.0.0.1:1666, por lo que el enrutamiento a través de un contenedor p4d separado requiere apuntar la URL de la PoC al host p4d.
Resultado honesto: contra las versiones reales, publicadas de Composer
2.9.5y2.9.6, la PoC no se activa actualmente, y el laboratorio informaINCONCLUSIVO / FALLO.
Ejecutar el Composer afectado (2.9.5) contra el manifiesto malicioso de la PoC lanza, dentro de Composer, antes de que se construya cualquier comando p4/shell:
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
PerforceDriver::initialize() lee $this->repoConfig['depot'] lo primero, pero la entrada de repositorio de la PoC solo proporciona type y url (sin clave depot). El controlador aborta en ese punto, por lo que el payload inyectado ;touch <marcador> en la URL nunca se alcanza y no se crea ningún marcador. El aislamiento de red no es la causa: el mismo error ocurre con salida de red completa.
Qué significa esto:
INCONCLUSIVO es una propiedad del payload de la PoC, no del entorno.depot (y, de forma realista, un endpoint p4d en vivo bajo el perfil full-lab). Refinar el payload hasta ese punto es desarrollo de exploits más allá de "montar el laboratorio" y queda intencionalmente fuera del alcance aquí.La "Salida Esperada" a continuación es el resultado previsto/idealizado de la PoC, mantenido como referencia; no es lo que el payload actual produce contra el controlador real.
Una demostración exitosa se ve aproximadamente así (las rutas e IDs variarán):
=== Iniciada PoC CVE-2026-40176 ===
- Versión de Composer 2.9.5: 2.9.5
- Versión de Composer 2.9.6: 2.9.6
Directorio temporal preparado: /tmp/cve20264176_5_20260610_142233
Escrito composer.json malicioso en /tmp/cve20264176_5_20260610_142233
Ejecutando Composer en /tmp/cve20264176_5_20260610_142233…
- Versión de Composer analizada: 2.9.5
- Marcador /tmp/cve20264176_5_.../poc_marker_5.txt creado con el ID esperado.
- La salida muestra actividad del controlador Perforce.
- Código de salida de ejecución afectada: 1
Directorio temporal preparado: /tmp/cve20264176_6_20260610_142233
Escrito composer.json malicioso en /tmp/cve20264176_6_20260610_142233
Ejecutando Composer en /tmp/cve20264176_6_20260610_142233…
- Versión de Composer analizada: 2.9.6
✘ Archivo marcador /tmp/cve20264176_6_.../poc_marker_6.txt no encontrado.
- La salida muestra actividad del controlador Perforce.
- Código de salida de ejecución corregida: 1
=== Finalizada PoC CVE-2026-40176 ===
=== RESULTADO DE LA PRUEBA: PASS (afectada exitosa, corregida falló) ===
Un código de salida de Composer distinto de cero es normal — composer update finalmente falla al obtener el paquete (falso). La prueba es el archivo marcador, no el estado de salida de Composer.
| Resultado | Significado |
|---|---|
| PASS (afectada exitosa, corregida falló) | Confirmado: 2.9.5 ejecutó el comando inyectado, 2.9.6 no. Se reproducen tanto la vulnerabilidad como su corrección. |
| INCONCLUSIVO / FALLO | Una o más verificaciones no coincidieron. Inspeccione las líneas ✓/✗ por ejecución: ruta de binario incorrecta, discrepancia de versión, marcador afectado faltante (diferencia de entorno/escapado), o la ejecución corregida creó inesperadamente un marcador. |
Causas comunes de un resultado inconcluso:
PerforceDriver.php:40 con Undefined array key "depot" — la configuración del repositorio carece de una clave depot, por lo que Composer nunca alcanza la ruta de construcción del comando p4. Esto es lo que sucede con el payload actual de la PoC contra las versiones reales 2.9.5/2.9.6 (consulte Estado de Reproducción).2.9.5 / 2.9.6.exec() de PHP está en un entorno limitado/deshabilitado.p4 (ruta del controlador no alcanzada).CVE2026-40176/
├── CVE202640176Test.php # La PoC: clase CVE202640176Test + punto de entrada
├── composer.json # Manifiesto base que la PoC lee/restaura en tiempo de ejecución
├── Dockerfile # Imagen del laboratorio: PHP CLI + Composer 2.9.5 y 2.9.6 fijados
├── docker-compose.yml # Ejecutor `poc` (+ `p4d` opcional bajo perfil full-lab)
├── .dockerignore # Reduce el contexto de construcción
├── README.md # Este archivo
└── .gitignore # Excluye estado local de agente/herramientas
Toda la PoC está en un archivo:
__construct() — almacena las dos rutas de binarios, acuña un ID de ejecución con marca de tiempo, captura una instantánea del composer.json original.run() — orquesta la verificación previa, la ejecución afectada, la ejecución corregida, la restauración y el veredicto.prepareTempDir() — crea un directorio de trabajo aislado por ejecución.writeComposerJson() — construye el manifiesto malicioso con la URL p4:// inyectada.runComposer() — ejecuta composer update (argumentos escapados con escapeshellarg()) y captura la salida + código de salida.validateRun() — verifica versión, archivo marcador y actividad del controlador Perforce.preflightVersion() — lee --version de un binario dado.composer.json original se restaura en un bloque finally independientemente del resultado.escapeshellarg() (para que la PoC no se inyecte accidentalmente en sus propias llamadas exec()). La vulnerabilidad reside una capa más profunda — en cómo Composer mismo construye el comando p4 — que es exactamente lo que el payload ataca.touch/echo en un marcador temporal único, lo que hace que la PoC sea segura de ejecutar repetidamente sin efectos secundarios en el host.exec("cd … && php …") y el payload con ;/&& asumen un shell tipo Unix; Windows no es compatible tal cual.mkdir. Los directorios temporales se crean con modo 0777; ajuste si se ejecuta en un entorno compartido.Composer X.Y.Z de la salida; banners inusuales de Composer podrían impedir la coincidencia.Este repositorio existe para entender y defenderse contra CVE-2026-40176.
composer install/update en archivos composer.json no confiables (por ejemplo, en pipelines de CI que procesen código fuente de terceros) sin aislamiento.2.9.5 / 2.9.6)2.9.6 (consulte el rastreador de seguridad de su distribución / la Base de Datos de Avisos de GitHub para obtener detalles autorizados de la corrección)Autor: Ikarolaborda · PoC fechada 2026-06-10.