
Локальный Docker-лаборатория, демонстрирующая CVE-2026-34234 — неавторизованное удалённое выполнение кода в веб-установщике CtrlPanel. Включает уязвимые и исправленные контейнеры, PoC-скрипты и анализ первопричин для исследований безопасности и защитной проверки.
Локальная Docker-лаборатория для демонстрации CVE-2026-34234 в CtrlPanel.
Этот репозиторий сравнивает:
vuln: CtrlPanel 1.1.1, зафиксированный по дайджестуpatched: CtrlPanel 1.2.0, зафиксированный по дайджестуЛаборатория работает только локально и привязывает сервисы к 127.0.0.1.
CVE-2026-34234 — это неаутентифицированное удалённое выполнение кода (RCE) в веб-установщике CtrlPanel.
Проблема вызвана двумя связанными ошибками:
install.lock.В этой лаборатории уязвимый контейнер выполняет безвредную команду для подтверждения и записывает её вывод внутри контейнера. Исправленный контейнер получает тот же запрос, но не создаёт файл подтверждения.
Ожидаемый результат:
vulnerable => proof file created
patched => no proof file
1.1.1Исходный уязвимый файл:
public/installer/src/functions/shell.php
Соответствующий код в 1.1.1:
function run_console(string $command, ...) {
$path = dirname(__DIR__, 4);
$handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", ...);
}
Проблема:
run_console() принимает одну строку команды оболочки.bash -c.Исходный уязвимый файл:
public/installer/src/forms/pterodactyl.php
Соответствующее поведение в 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'", ...);
Проблема:
url, key и clientkey берутся из POST-данных установщика.В рекомендации указано, что public/installer/index.php проверял install.lock только после загрузки/выполнения логики форм установщика. Это делало обработчики установщика доступными даже на уже установленных экземплярах.
Исправление перемещает проверку install.lock до загрузки обработчиков форм.
Исправленное поведение:
if (file_exists('../../install.lock')) {
exit("The installation has been completed already. Please delete the File 'install.lock' to re-run");
}
Исходный исправленный файл:
public/installer/src/functions/shell.php
Соответствующий код в 1.2.0:
function run_console(array $command, ...): string {
$cwd = $cwd ?? $path;
$handle = proc_open($command, $descriptors, $pipes, $cwd, null, $options);
}
Почему это исправляет проблему:
run_console() теперь принимает массив в стиле argv.$(), остаётся буквальным вводом вместо синтаксиса оболочки.Исправленное поведение форм в 1.2.0 использует выполнение команд в стиле массива:
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
Сервисы:
vuln: реальный CtrlPanel 1.1.1patched: реальный CtrlPanel 1.2.0fake-api: локальный фейковый API Pterodactyl, используемый только для удовлетворения проверок установщикаmysql_vuln / mysql_patched: отдельные экземпляры MariaDBredis_vuln / redis_patched: отдельные экземпляры RedisЛаборатория не изменяет исходный код приложения CtrlPanel.
Docker-файлы только оборачивают оригинальную точку входа контейнера для нормализации прав выполнения Docker Desktop:
/var/www/html/storage
/var/www/html/bootstrap/cache
После исправления прав обёртка выполняет оригинальную точку входа продукта.
Основной PoC:
poc/poc_http_only.py
Свойства:
docker execid, whoami, hostnameВспомогательный скрипт:
poc/poc_lab.py
Назначение:
docker compose execФайл подтверждения внутри контейнера приложения:
/var/www/html/storage/logs/cve_2026_34234_proof.txt
Начните с чистого состояния лаборатории:
docker compose down -v --remove-orphans
docker compose up -d --build
Дождитесь, пока контейнеры приложения запустятся, затем выполните:
python3 poc/poc_lab.py
Ожидаемый вывод:
== 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
Отправьте HTTP-only PoC на уязвимый целевой сервер:
python3 poc/poc_http_only.py --target http://127.0.0.1:8081
Проверьте подтверждение вручную:
docker compose exec vuln sh -lc 'cat /var/www/html/storage/logs/cve_2026_34234_proof.txt'
Ожидаемое подтверждение:
uid=1000(laravel) gid=1000(laravel) groups=1000(laravel)
laravel
<container-hostname>
Выполните тот же запрос к исправленному:
python3 poc/poc_http_only.py --target http://127.0.0.1:8082
Проверьте поведение исправленного:
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"'
Ожидается:
no proof file
Удалите контейнеры, сети и тома лаборатории:
docker compose down -v
Этот репозиторий предоставляется только для образовательных исследований в области безопасности и защитной валидации.
Все демонстрации предназначены для запуска в предоставленной локальной лабораторной среде Docker.
Доказательство концепции избегает деструктивных действий, сохранения состояния, кражи учётных данных, эксфильтрации данных и нацеливания на реальные системы.
Не используйте этот проект против какой-либо системы без явного разрешения. Автор не несёт ответственности за неправильное использование или ущерб, возникший в результате использования этого материала.