
Laboratorio Docker local que demuestra la CVE-2026-34234 RCE no autenticada en el instalador web de CtrlPanel. Incluye contenedores vulnerables y parcheados, scripts PoC y análisis de causa raíz para investigación de seguridad y validación defensiva.
Laboratorio Docker local para demostrar CVE-2026-34234 en CtrlPanel.
Este repositorio compara:
vuln: CtrlPanel 1.1.1 fijado por digestpatched: CtrlPanel 1.2.0 fijado por digestEl laboratorio es solo local y vincula servicios a 127.0.0.1.
CVE-2026-34234 es un RCE no autenticado en el instalador web de CtrlPanel.
El problema es causado por dos errores encadenados:
install.lock.En este laboratorio, el contenedor vulnerable ejecuta un comando de prueba inofensivo y escribe su salida dentro del contenedor. El contenedor parcheado recibe la misma solicitud pero no crea el archivo de prueba.
Resultado esperado:
vulnerable => proof file created
patched => no proof file
1.1.1Archivo original vulnerable:
public/installer/src/functions/shell.php
Código upstream relevante en 1.1.1:
function run_console(string $command, ...) {
$path = dirname(__DIR__, 4);
$handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", ...);
}
Problema:
run_console() acepta una cadena de comando de shell.bash -c.Archivo original vulnerable:
public/installer/src/forms/pterodactyl.php
Comportamiento upstream relevante en 1.1.1:
run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'", ...);
run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'", ...);
run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'", ...);
Problema:
url, key y clientkey se originan en los datos POST del instalador.El aviso indica que public/installer/index.php verificaba install.lock solo después de cargar/ejecutar la lógica del formulario del instalador. Eso hacía que los manejadores del instalador fueran accesibles incluso en instancias ya instaladas.
La corrección mueve la verificación de install.lock antes de que se carguen los manejadores de formularios.
Comportamiento parcheado:
if (file_exists('../../install.lock')) {
exit("The installation has been completed already. Please delete the File 'install.lock' to re-run");
}
Archivo parcheado original:
public/installer/src/functions/shell.php
Código upstream relevante en 1.2.0:
function run_console(array $command, ...): string {
$cwd = $cwd ?? $path;
$handle = proc_open($command, $descriptors, $pipes, $cwd, null, $options);
}
Por qué esto soluciona el problema:
run_console() ahora acepta un array estilo argv.$() permanece como entrada literal en lugar de sintaxis de shell.El comportamiento del formulario parcheado en 1.2.0 usa ejecución de comandos estilo array:
run_console(['php', 'artisan', 'settings:set', 'PterodactylSettings', 'panel_url', $url], ...);
run_console(['php', 'artisan', 'settings:set', 'PterodactylSettings', 'admin_token', $key], ...);
run_console(['php', 'artisan', 'settings:set', 'PterodactylSettings', 'user_token', $clientkey], ...);
127.0.0.1:8081 -> vulnerable CtrlPanel 1.1.1
127.0.0.1:8082 -> patched CtrlPanel 1.2.0
127.0.0.1:9100 -> fake Pterodactyl API
Servicios:
vuln: CtrlPanel real 1.1.1patched: CtrlPanel real 1.2.0fake-api: API falsa local de Pterodactyl usada solo para satisfacer las comprobaciones del instaladormysql_vuln / mysql_patched: instancias separadas de MariaDBredis_vuln / redis_patched: instancias separadas de RedisEl laboratorio no modifica el código fuente de la aplicación CtrlPanel.
Los Dockerfiles solo envuelven el entrypoint original del contenedor para normalizar los permisos de ejecución de Docker Desktop para:
/var/www/html/storage
/var/www/html/bootstrap/cache
Después de corregir los permisos, el wrapper ejecuta el entrypoint original del producto.
PoC principal:
poc/poc_http_only.py
Propiedades:
docker execid, whoami, hostnameScript auxiliar:
poc/poc_lab.py
Propósito:
docker compose execArchivo de prueba dentro del contenedor de la aplicación:
/var/www/html/storage/logs/cve_2026_34234_proof.txt
Iniciar desde un estado limpio del laboratorio:
docker compose down -v --remove-orphans
docker compose up -d --build
Esperar hasta que los contenedores de la aplicación estén activos, luego ejecutar:
python3 poc/poc_lab.py
Salida esperada:
== Testing vulnerable ==
proof_exists: True
result: PASS expected_proof=True
== Testing patched ==
proof_exists: False
result: PASS expected_proof=False
[+] Expected result reached:
vulnerable => proof file created
patched => no proof file
Enviar el PoC solo HTTP al objetivo vulnerable:
python3 poc/poc_http_only.py --target http://127.0.0.1:8081
Verificar la prueba manualmente:
docker compose exec vuln sh -lc 'cat /var/www/html/storage/logs/cve_2026_34234_proof.txt'
Prueba esperada:
uid=1000(laravel) gid=1000(laravel) groups=1000(laravel)
laravel
<container-hostname>
Ejecutar la misma solicitud contra el parcheado:
python3 poc/poc_http_only.py --target http://127.0.0.1:8082
Verificar el comportamiento parcheado:
docker compose exec patched sh -lc 'test -f /var/www/html/storage/logs/cve_2026_34234_proof.txt && cat /var/www/html/storage/logs/cve_2026_34234_proof.txt || echo "no proof file"'
Esperado:
no proof file
Eliminar contenedores, redes y volúmenes del laboratorio:
docker compose down -v
Este repositorio se proporciona únicamente para investigación educativa en seguridad y validación defensiva.
Todas las demostraciones están diseñadas para ejecutarse dentro del entorno de laboratorio Docker local proporcionado. La prueba de concepto evita acciones destructivas, persistencia, robo de credenciales, exfiltración de datos y orientación al mundo real.
No use este proyecto contra ningún sistema sin autorización explícita. El autor no es responsable por el mal uso o daños derivados de este material.