
Laboratório Docker local demonstrando a CVE-2026-34234 RCE não autenticada no instalador web do CtrlPanel. Inclui contêineres vulneráveis e corrigidos, scripts de PoC e análise de causa raiz para pesquisa de segurança e validação defensiva.
Laboratório Docker local para demonstração do CVE-2026-34234 no CtrlPanel.
Este repositório compara:
vuln: CtrlPanel 1.1.1 fixado por digestpatched: CtrlPanel 1.2.0 fixado por digestO laboratório é apenas local e vincula serviços a 127.0.0.1.
CVE-2026-34234 é um RCE não autenticado no instalador web do CtrlPanel.
O problema é causado por dois bugs encadeados:
install.lock.Neste laboratório, o container vulnerável executa um comando de prova inofensivo e grava sua saída dentro do container. O container corrigido recebe a mesma requisição, mas não cria o arquivo de prova.
Resultado esperado:
vulnerable => proof file created
patched => no proof file
1.1.1Arquivo vulnerável original:
public/installer/src/functions/shell.php
Código relevante upstream no 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() aceita uma string de comando do shell.bash -c.Arquivo vulnerável original:
public/installer/src/forms/pterodactyl.php
Comportamento upstream relevante no 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 e clientkey são originados de dados POST do instalador.O advisory afirma que public/installer/index.php verificava install.lock apenas após carregar/executar a lógica do formulário do instalador. Isso tornava os manipuladores do instalador acessíveis mesmo em instâncias já instaladas.
A correção move a verificação de install.lock para antes do carregamento dos manipuladores do formulário.
Comportamento corrigido:
if (file_exists('../../install.lock')) {
exit("The installation has been completed already. Please delete the File 'install.lock' to re-run");
}
Arquivo corrigido original:
public/installer/src/functions/shell.php
Código upstream relevante no 1.2.0:
function run_console(array $command, ...): string {
$cwd = $cwd ?? $path;
$handle = proc_open($command, $descriptors, $pipes, $cwd, null, $options);
}
Por que isso corrige o problema:
run_console() agora aceita um array estilo argv.$() permanece como entrada literal em vez de sintaxe do shell.Comportamento corrigido do formulário no 1.2.0 usa execução de comando 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
Serviços:
vuln: CtrlPanel real 1.1.1patched: CtrlPanel real 1.2.0fake-api: API Pterodactyl falsa local usada apenas para satisfazer as verificações do instaladormysql_vuln / mysql_patched: instâncias MariaDB separadasredis_vuln / redis_patched: instâncias Redis separadasO laboratório não modifica o código-fonte da aplicação CtrlPanel.
Os Dockerfiles apenas envolvem o entrypoint original do container para normalizar as permissões de tempo de execução do Docker Desktop para:
/var/www/html/storage
/var/www/html/bootstrap/cache
Após corrigir as permissões, o wrapper executa o entrypoint original do produto.
Prova de Conceito principal:
poc/poc_http_only.py
Propriedades:
docker execid, whoami, hostnameScript auxiliar:
poc/poc_lab.py
Propósito:
docker compose execArquivo de prova dentro do container da aplicação:
/var/www/html/storage/logs/cve_2026_34234_proof.txt
Inicie a partir de um estado limpo do laboratório:
docker compose down -v --remove-orphans
docker compose up -d --build
Aguarde até que os containers da aplicação estejam ativos, então execute:
python3 poc/poc_lab.py
Saída 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
Envie a PoC apenas HTTP para o alvo vulnerável:
python3 poc/poc_http_only.py --target http://127.0.0.1:8081
Verifique a prova manualmente:
docker compose exec vuln sh -lc 'cat /var/www/html/storage/logs/cve_2026_34234_proof.txt'
Prova esperada:
uid=1000(laravel) gid=1000(laravel) groups=1000(laravel)
laravel
<container-hostname>
Execute a mesma requisição contra o corrigido:
python3 poc/poc_http_only.py --target http://127.0.0.1:8082
Verifique o comportamento corrigido:
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
Remova containers, redes e volumes do laboratório:
docker compose down -v
Este repositório é fornecido apenas para fins educacionais de pesquisa em segurança e validação defensiva.
Todas as demonstrações destinam-se a ser executadas dentro do ambiente de laboratório Docker local fornecido. A prova de conceito evita ações destrutivas, persistência, roubo de credenciais, exfiltração de dados e direcionamento a sistemas reais.
Não use este projeto contra qualquer sistema sem autorização explícita. O autor não é responsável por uso indevido ou danos resultantes deste material.