Статья CVE-2026-43633: десериализация в HestiaCP - unauthenticated RCE через PHP/Node.js session mismatch

Вскрытый серверный модуль на антистатическом коврике под жёстким верхним светом. Диагностический терминал отображает глитч-текст об уязвимости, рука в перчатке касается отладочного разъёма щупом.


На пентесте shared-хостинга я наткнулся на HestiaCP 1.9.3 с включённым web terminal на порту 8083. Два запроса через curl - root shell, ноль аутентификации. Просто вдумайтесь: два запроса.

CVE-2026-43633 (CVSS 9.5, CWE-502) бьёт в архитектурную ошибку, которую видишь раз - запоминаешь навсегда: PHP пишет контролируемые атакующим данные в сессионный файл с length-prefixed сериализацией, а Node.js-компонент web terminal парсит тот же файл наивным split(), не понимая границ значений. По данным Mercury ISS, около 200 000 инстансов HestiaCP торчат в интернет, и 10-15% из них крутят активный web terminal - тот самый уязвимый компонент.

Бизнес-логика атаки: зачем атакующему root на хостинг-панели​

Root shell на HestiaCP - это не один скомпрометированный сервер. Это вся инфраструктура хостинга целиком. Панель управляет доменами, почтой, базами данных и DNS-записями десятков клиентов одновременно. Что получает атакующий:
  • Полный контроль над сайтами всех пользователей панели - web shell, подмена контента, кража исходников и бэкапов
  • Прямой доступ к MySQL/PostgreSQL через CLI: v-list-databases и v-list-db-users выводят все учётные данные без дополнительных привилегий
  • Перехват и перенаправление почты через модификацию конфигурации Exim/Dovecot
  • Плацдарм для lateral movement - VPS с хостинг-панелью нередко сидит в инфраструктуре, где есть сетевой доступ к другим серверам заказчика
В криминальной экономике скомпрометированные хостинг-панели идут как инфраструктура для массового фишинга (сотни доменов с чистой репутацией), криптомайнинга и как товар для initial access broker (IAB) - продажа пакетного доступа к десяткам доменов одной транзакцией. Root RCE без аутентификации на хостинг-панели - высоколиквидный актив на даркнет-маркетах.

Архитектура уязвимости: PHP и Node.js делят сессионные файлы​

1784486358595.webp

HestiaCP - open-source форк VestaCP для управления веб-хостингом на Debian и Ubuntu. Архитектура гибридная: веб-интерфейс на порту 8083 работает через Nginx + PHP-FPM, а web terminal - отдельный Node.js-процесс, который даёт интерактивный shell через WebSocket.

Корень проблемы - оба компонента работают с одними и теми же сессионными файлами в каталоге $HESTIA/data/sessions/. PHP записывает данные в формате нативной сериализации: key|s:LENGTH:"VALUE";, где LENGTH - количество байт в VALUE. Node.js читает те же файлы, но PHP-десериализатор не использует. Вместо этого - текстовые операции split(), которые понятия не имеют о границах сериализации.

Это и есть session mismatch (session confusion) - два парсера с несовместимой семантикой на одном потоке данных. PHP корректно разграничивает значения по длине в байтах. Node.js ищет символы-разделители позиционно, не учитывая, что |, s: и " могут быть частью значения другого поля. Ни один компонент по отдельности не содержит уязвимости - проблема возникает исключительно на стыке. Классическая ситуация: каждый прав, а вместе - дыра.

PHP-сторона: X-Forwarded-For попадает в сессию без санитизации​

На каждом page load файл web/inc/main.php конкатенирует HTTP-заголовки прямо в $_SESSION. Nginx в дефолтной конфигурации HestiaCP передаёт X-Forwarded-For от клиента к PHP-FPM без переопределения:
PHP:
$user_combined_ip = "";
if (isset($_SERVER["REMOTE_ADDR"])) {
    $user_combined_ip = $_SERVER["REMOTE_ADDR"];
}
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
    $user_combined_ip .= "|" . $_SERVER["HTTP_X_FORWARDED_FOR"];
}
$_SESSION["user_combined_ip"] = $user_combined_ip;
Атакующий контролирует содержимое X-Forwarded-For. PHP записывает его в сессионный файл в формате user_combined_ip|s:LENGTH:"VALUE";. Благодаря length-prefixed формату PHP корректно обрабатывает вложенные разделители | и " - он считает байты, а не ищет символы. С точки зрения PHP-сериализатора вложенные спецсимволы - просто часть значения строки, ограниченной счётчиком длины. Проблемы нет. До тех пор, пока файл читает PHP.

Node.js: наивный split() вместо десериализатора​

Web terminal извлекает имя пользователя из raw-содержимого сессионного файла текстовыми операциями:
JavaScript:
const file = readFileSync(`${HESTIA}/data/sessions/sess_${sessionID}`);
const session = file.toString();
const login = session.split('user|s:')[1].split('"')[1];
const impersonating = session.split('look|s:')[1].split('"')[1];
const username = impersonating.length > 0 ? impersonating : login;
Разберём, почему это ломается. split('user|s:') разбивает весь файл по подстроке user|s: и берёт фрагмент после первого совпадения. Затем split('"')[1] извлекает текст между первой парой кавычек. Проблема: split() ищет подстроку в ЛЮБОМ месте файла, включая внутри значений других полей.

Если атакующий отправит заголовок X-Forwarded-For: user|s:4:"root", PHP запишет в сессионный файл строку вида: ...user_combined_ip|s:27:"172.17.0.1|user|s:4:"root"";look|s:0:"";.... PHP-парсер видит это как одно значение длиной 27 байт. А Node.js видит подстроку user|s:4:"root" и извлекает из неё root как имя пользователя. Терминал открывается от имени root.

И ещё один подарок: Node.js не проверяет, аутентифицирована ли сессия. Проверяется только существование сессионного файла и возможность выдернуть из него username. А сессия создаётся при любом обращении к PHP-интерфейсу - достаточно открыть страницу /login/ без ввода каких-либо учётных данных.

Эксплуатация CVE-2026-43633: от HTTP-заголовка до root shell​

1784486390564.webp

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Два запроса до root​

Шаг 1 - создание отравленной сессии. Отправляем GET на любую страницу HestiaCP с injection payload в X-Forwarded-For:
Bash:
curl -sk -H 'X-Forwarded-For: user|s:4:"root"' https://target:8083/login/ -c cookies.txt
PHP создаёт сессионный файл sess_<ID> и записывает значение user_combined_ip с внедрённым payload. Результат в файле: ...user_combined_ip|s:27:"172.17.0.1|user|s:4:"root"";look|s:0:"";.... Для PHP - штатная строка IP-адреса длиной 27 байт. Для Node.js - готовый injection primitive.

Шаг 2 - подключение к web terminal. Используем session cookie из cookies.txt для WebSocket-подключения к web terminal. Node.js читает сессионный файл, выполняет split('user|s:'), находит инжектированный фрагмент user|s:4:"root" внутри user_combined_ip, вызывает split('"')[1], получает строку root и открывает терминальную сессию от имени root. Без пароля, без проверки аутентификации - её на уровне web terminal просто не существует.

Итог: интерактивный shell с привилегиями root через два HTTP-запроса. Время от первого запроса до shell - секунды.

CVE-2026-43634: IP spoofing для операций без следов в логах​

1784486415781.webp

Параллельно с RCE в HestiaCP 1.2.0-1.9.4 сидит IP spoofing (CVE-2026-43634, CVSS 8.7, CWE-348). Обработчик логина и сессионный трекер доверяют HTTP-заголовку CF-Connecting-IP без проверки, что запрос реально пришёл от Cloudflare. Единственная валидация - синтаксическая проверка формата IP через FILTER_VALIDATE_IP(https://www.php.net/manual/en/filter.filters.validate.php). Любой клиент, подключающийся напрямую к порту 8083, может подставить произвольный IP.

Что это даёт для OPSEC атакующего:
  • Все записи в auth-логах содержат поддельный IP - реальный адрес не фиксируется нигде
  • Fail2ban банит поддельный IP вместо настоящего
  • Функция v-add-firewall-ban явно отказывается банить 127.0.0.1 и адреса самого сервера - атакующий подставляет loopback и становится неблокируемым
Комбинация CVE-2026-43633 и CVE-2026-43634 даёт unauthenticated root RCE без следов в логах. По данным Mercury ISS, реальный IP-адрес атакующего не появляется ни в auth-логах, ни в fail2ban, ни в audit trail. Для red team с OPSEC-требованиями - идеальный вектор initial access.

Место в kill chain и ограничения техники​

В терминах MITRE ATT&CK эксплуатация CVE-2026-43633 проходит через следующие этапы:
  1. Exploit Public-Facing Application (T1190, Initial Access) - HTTP-запрос с payload в X-Forwarded-For создаёт отравленную сессию
  2. Web Session Cookie (T1550.004) - использование session cookie для доступа к web terminal с подменённым username
  3. Unix Shell (T1059.004, Execution) - интерактивный root shell через WebSocket
  4. Exploitation for Privilege Escalation (T1068) - session mismatch сразу даёт root без промежуточных шагов
После получения root shell атакующий переходит к post-exploitation: persistence через Web Shell (T1505.003) или cron, дамп кредов из /etc/shadow (T1003.008), lateral movement в инфраструктуре клиентов хостинга через SSH-ключи и данные из баз.

СценарийПрименимостьКомментарий
Внешний пентест, black boxДаFingerprinting HestiaCP тривиален
Внутренний пентест, grey boxДаУчётные данные не требуются
Red team, OPSEC-критичныйДаВ связке с CVE-2026-43634 логи чистые
Массовый recon (bug bounty)ОграниченноWeb terminal включён на 10-15% инстансов

Историческая справка: HestiaCP уже имела проблемы с привилегиями - CVE-2022-2626 (CVSS 7.2, CWE-266) позволяла privilege escalation через принадлежность пользователя admin к одноимённой группе с root-правами в дефолтном /etc/sudoers Ubuntu. Отдельно: исследователи из Project Black описали broken authorization в механизме panel cron jobs, позволяющую low-privileged пользователю сменить пароль администратора через v-change-user-password. Паттерн повторяется - архитектурные решения, унаследованные от VestaCP, раз за разом создают поверхность для privilege escalation.

Ограничения​

  • Узкий attack surface. Web terminal - опциональный компонент. На 85-90% инстансов отключён. При массовом сканировании конверсия из "найден HestiaCP" в "эксплуатация возможна" - порядка 10-15%
  • Строгий диапазон версий. Только 1.9.0-1.9.4. Более ранние версии не содержат уязвимую реализацию web terminal в этом варианте
  • Патч в main branch. Те, кто собирает HestiaCP из исходников, уже защищены. Ожидается официальный релиз
  • WAF/reverse proxy. Если перед HestiaCP стоит proxy, перезаписывающий X-Forwarded-For, payload не достигнет сессионного файла
  • Forensic-артефакт. Отравленный сессионный файл содержит нетипичное значение user_combined_ip с подстроками user|s: - простой grep обнаружит эксплуатацию post-factum
  • Нет готового Nuclei-шаблона. В nuclei-templates пока отсутствует template для CVE-2026-43633 (в отличие от CVE-2026-41940 для cPanel, где template уже доступен)

Fingerprinting уязвимых инстансов HestiaCP​

Определение уязвимого инстанса при пентесте хостинг-панелей укладывается в четыре шага:
  1. Обнаружение порта 8083. Дефолтный для HestiaCP. nmap -sS -p 8083 target или массовый recon через httpx -p 8083 -title -status-code на списке целей
  2. Идентификация панели. Ответ на curl -sI https://target:8083/ содержит Set-Cookie: PHPSESSID, HTML страницы /login/ содержит характерный title и CSS-пути с упоминанием HestiaCP
  3. Определение версии. Footer веб-интерфейса, CSS/JS-файлы с версией в query-параметре, иногда заголовок ответа. При black box без аутентификации - версия может быть недоступна напрямую, но CSS-файлы часто содержат хэш или номер билда
  4. Проверка web terminal. Попытка WebSocket-подключения к эндпоинту web terminal. Если компонент отключён - connection refused или 404. Если включён - WebSocket handshake пройдёт (но сессия без injection не даст shell)

Детект и митигация​

Обнаружение эксплуатации​

IoC для blue team и администраторов:
  • Подстроки user|s: или look|s: внутри значения user_combined_ip в файлах $HESTIA/data/sessions/sess_* - прямой индикатор session injection
  • WebSocket-подключения к web terminal с session ID, для которого нет записи об успешной аутентификации
  • HTTP-запросы к порту 8083 с X-Forwarded-For, содержащим спецсимволы PHP-сериализации: |s:, :", ";
  • Запросы с CF-Connecting-IP не от IP-адресов Cloudflare (индикатор CVE-2026-43634)

Чеклист для администратора HestiaCP​

  1. Проверить версию: v-list-sys-info | grep VERSION - если 1.9.0-1.9.4, инстанс уязвим
  2. Отключить web terminal немедленно: v-delete-sys-web-terminal
  3. Ограничить порт 8083 доверенными IP на уровне файрвола: iptables -A INPUT -p tcp --dport 8083 ! -s TRUSTED_IP -j DROP
  4. Проверить сессии на компрометацию: grep -rl 'user|s:' $HESTIA/data/sessions/
  5. Аудит auth-логов: искать source IP 127.0.0.1 или IP самого сервера - индикатор CVE-2026-43634
  6. Обновить HestiaCP до последнего коммита main branch или дождаться официального релиза
  7. Добавить в конфигурацию Nginx принудительную перезапись заголовка: proxy_set_header X-Forwarded-For $remote_addr;
CVE-2026-43633 - не экзотика. Это результат архитектурного решения, которое кочует из хостинг-панели в хостинг-панель: разные runtime делят состояние через файловую систему, и каждый интерпретирует данные по-своему. VestaCP, из которой форкнули HestiaCP, уже имела проблемы с привилегиями - CVE-2022-2626. Паттерн архитектурного наследования уязвимостей воспроизводится при каждом форке.

Я называю этот класс багов session confusion - и он хуже классической CWE-502 с unserialize() на пользовательском вводе. Тут нет вызова unserialize(). Два парсера с несовместимой семантикой на одном файле, и каждый по отдельности работает корректно. Ни SAST, ни DAST такое не найдут: SAST анализирует компоненты изолированно, DAST не знает, какой payload в каком заголовке вызовет cross-runtime confusion. Единственный способ - ручной анализ межкомпонентного взаимодействия: какие файлы (или shared memory, или БД) пишет один процесс и читает другой. Совпадают ли форматы парсинга. При аудите любой панели с гибридной архитектурой - PHP + Node.js, Python + Go, Ruby + JavaScript - ищите общие хранилища состояния первым делом. Это самая короткая дорога к unauthenticated RCE, и я уверен, что аналогичные session confusion баги прячутся в десятках менее популярных панелей - просто туда пока никто не смотрел.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab