На проверке CVE-2026-45631: Dokploy уязвимость — захват PaaS-платформы через hardcoded secret и поддельный JWT

Сергей Попов

Администратор
30.12.2015
6 217
6 957
Специализация
  1. OSINT
  2. Веб-безопасность
Статус верификации
  1. ✓ Verified
Расколотая восковая печать с оттиском JWT-токена и надписью «better-auth-secret-123456789» лежит на чёрном антистатическом коврике, в трещину воткнуто тонкое лезвие. Рядом — поддельный дубликат печ...


CVSS 10.0 из 10.0. Максимальный балл. Одна строка в исходном коде Dokploy - better-auth-secret-123456789 - превращает каждую дефолтную инсталляцию этой self-hosted PaaS-платформы в pre-auth RCE. Неаутентифицированный атакующий подделывает JWT для верификации email, получает сессию администратора и выполняет произвольные команды на хосте через встроенный SSH-терминал. По данным CISA-ADP, эксплуатация автоматизируема, классификация exploitation - poc (proof-of-concept существует), техническое воздействие - тотальное. Публичный exploit-код в Exploit-DB на момент публикации не обнаружен; код ниже - демонстрационная реконструкция.

Бизнес-логика атаки: зачем атакующему self-hosted PaaS​

Dokploy - open-source альтернатива Heroku и Railway для деплоя контейнеризированных приложений на собственном сервере. Типичная установка: VPS, Docker, панель управления на порту 3000. Администратор через веб-интерфейс деплоит приложения, базы данных, настраивает домены и SSL.

Для атакующего компрометация PaaS-панели - это не один сервис. Это всё сразу:
  • Полный контроль над всеми приложениями на хосте: исходники, переменные окружения с API-ключами, токенами БД, секретами сторонних сервисов
  • RCE на хосте через встроенный SSH-терминал - без отдельного побега из контейнера
  • Persistence - добавление SSH-ключа, создание дополнительного администратора, модификация деплоев для установки бэкдоров
  • Lateral movement - доступ к внутренней сети через скомпрометированный хост, который часто стоит в production-сегменте
Владельцы self-hosted PaaS нередко выставляют панель управления в интернет без VPN или IP-whitelist, полагаясь исключительно на аутентификацию приложения. А зря.

Техническая анатомия CVE-2026-45631​

Hardcoded BETTER_AUTH_SECRET как корень проблемы​

По данным NVD и GitHub Security Advisory GHSA-w3gm-rc4p-9rhj, уязвимость присутствует в версиях Dokploy от 0.27.0 до 0.29.3 (не включительно). Проблема классифицирована как CWE-798 - Use of Hard-coded Credentials: продукт содержит жёстко закодированный криптографический ключ.

Dokploy использует библиотеку Better Auth для управления сессиями. При инициализации приложение ищет переменную окружения BETTER_AUTH_SECRET. Не нашло - подставляет fallback: строку better-auth-secret-123456789. А в стандартном Docker-деплое переменная не устанавливается.

Эта строка - ключ подписи JWT-токенов. Fallback лежит в публичном репозитории на GitHub. Атакующий знает ключ подписи для каждой инсталляции Dokploy, где администратор не переопределил переменную. Просто открыл исходники - и всё.

Паттерн hardcoded secret повторяется в десятках проектов. В Flowise (CVE-2026-56269) дефолтный TOKEN_HASH_SECRET - Secre$t, в Go Restful API Boilerplate (GHSA-mqq6-462x-jxmm) AUTH_JWT_SECRET зашит как random. Но severity у Dokploy радикально выше: CVSS 10.0 (v3.1) против 4.3 (CVSS 4.0) у Flowise. Причина - SSH-терминал в Dokploy даёт прямой путь от поддельного JWT до выполнения команд на хосте, без промежуточных эксплойтов.

Цепочка эксплуатации: от секрета до RCE на хосте​

Атака разворачивается в четыре шага:
  1. Получение секрета - строка better-auth-secret-123456789 берётся из публичного репозитория Dokploy
  2. Форжинг JWT - атакующий создаёт поддельный JWT для верификации email, указывая email существующего администратора
  3. Auto-sign-in - при успешной верификации Dokploy автоматически создаёт сессию с правами пользователя, чей email был «подтверждён». Атакующий получает администраторскую сессию без ввода пароля
  4. RCE через SSH-терминал - встроенный SSH-терминал в веб-интерфейсе Dokploy позволяет администратору выполнять команды на хосте. Атакующий с админ-сессией просто открывает терминал и делает что хочет
CVSS-вектор CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H подтверждает каждый аспект цепочки:

КомпонентЗначениеЧто означает
AV:NNetworkАтака по сети
AC:LLowСекрет публичен, сложность минимальна
PR:NNoneПривилегии не требуются
UI:NNoneДействие пользователя не нужно
S:CChangedВыход за границу приложения на хост
C:H / I:H / A:HHighПолная компрометация

S:C (Scope: Changed) - тут самое интересное: уязвимость в веб-приложении позволяет атакующему выйти за его пределы и получить контроль над хост-системой.

Место в kill chain: от initial access до контроля хоста​

В терминологии MITRE ATT&CK цепочка эксплуатации CVE-2026-45631 покрывает несколько тактик:

ЭтапТехника ATT&CKОписание
Initial AccessExploit Public-Facing Application (T1190)Эксплуатация публично доступной панели Dokploy
Credential AccessCredentials In Files (T1552.001)Hardcoded-секрет в исходном коде
Credential AccessForge Web Credentials (T1606)Создание поддельного JWT
Privilege EscalationValid Accounts (T1078)Использование администраторской сессии
ExecutionDeploy Container (T1610)Выполнение команд, управление контейнерами

[Применимо: внешний пентест, self-hosted PaaS на публичном IP, black box / grey box]

Отличие от типичных JWT-уязвимостей: здесь не нужно перехватывать существующий токен или эксплуатировать алгоритмическую слабость вроде alg: none (классика атак на JWT с подменой алгоритма). Атакующий генерирует валидный токен с нуля, потому что ключ подписи - публично известная строка. Никаких промежуточных шагов, никакого MitM.

Пошаговая эксплуатация CVE-2026-45631​

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

). В некоторых конфигурациях email можно вытянуть через утечки информации в ответах API.

Форжинг JWT и захват администратора​

Зная секрет и email администратора, атакующий генерирует JWT для верификации. Ниже - иллюстративный пример на Python (не тестировался против реальной инсталляции; точная структура payload, включая обязательные claims вроде aud или nonce, зависит от версии Better Auth и может потребовать дополнительного реверса):
Python:
import jwt

SECRET = "better-auth-secret-123456789"
payload = {
    "sub": "admin@target.com",
    "type": "email_verification",
    "iat": 1717000000,
    "exp": 9999999999
}
token = jwt.encode(payload, SECRET, algorithm="HS256")
print(token)
Полученный токен подставляется в запрос к эндпоинту верификации email (обычно /api/auth/verify-email?token=<JWT>). При успешной верификации Dokploy создаёт сессию - атакующий получает cookie или bearer-токен с правами администратора.

Дальше - SSH-терминал в веб-интерфейсе, id, whoami, потом что угодно. Dokploy обычно работает от root или с доступом к Docker-сокету - так что это полный контроль над сервером.

Детектирование и митигация​

Быстрая проверка своей инсталляции:
Bash:
docker exec <dokploy_container> env | grep BETTER_AUTH_SECRET
Переменная отсутствует в выводе - инсталляция уязвима. Присутствует со значением, отличным от better-auth-secret-123456789 - секрет переопределён, можно выдохнуть.

Стоит также проверить логи на обращения к эндпоинту верификации email. Легитимная верификация происходит один раз при регистрации; повторные запросы от незнакомых IP - индикатор попытки эксплуатации.

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

  1. Определить текущую версию: если ниже 0.29.3 - обновлять немедленно
  2. Обновить Dokploy до 0.29.3 или выше (исправление в PR #4374)
  3. Явно задать BETTER_AUTH_SECRET: openssl rand -base64 32 - и прописать в переменные окружения контейнера (минимум 32 символа)
  4. Проверить логи на обращения к /api/auth/verify-email от IP, не принадлежащих легитимным пользователям
  5. Проверить список администраторов в панели - не появились ли незнакомые аккаунты
  6. Ограничить сетевой доступ к панели: VPN, IP-whitelist на firewall, или Cloudflare Access / Authelia перед Dokploy
  7. Аудит SSH-ключей на хосте: cat ~/.ssh/authorized_keys - убедиться в отсутствии чужих ключей
  8. Проверить crontab и systemd-юниты на хосте на предмет бэкдоров
  9. Ротировать все секреты (API-ключи, пароли БД, токены), хранившиеся в переменных окружения приложений Dokploy - после компрометации они считаются утёкшими

Ограничения и когда техника не работает​

  • Версия < 0.27.0: BETTER_AUTH_SECRET fallback не существует, эксплуатация CVE-2026-45631 невозможна
  • Версия >= 0.29.3: баг исправлен, fallback удалён
  • Явно заданный секрет: если администратор установил собственное значение BETTER_AUTH_SECRET - hardcoded-строка бесполезна, атакующему потребуется bruteforce или отдельная утечка кастомного секрета
  • Панель за VPN или IP-whitelist: даже при уязвимой версии атакующий не доберётся до эндпоинта без сетевого доступа
  • Неизвестный email администратора: для форжинга JWT нужен email целевого аккаунта - без него payload невалиден. На практике email часто угадываем, так что ограничение скорее теоретическое
CWE-798 - одна из старейших категорий уязвимостей, и она продолжает всплывать в современных проектах с завидным упорством. Flowise с Secre$t, Go-бойлерплейты с random, LightRAG с дефолтным DEFAULT_TOKEN_SECRET - один и тот же антипаттерн. Разница в последствиях: у большинства hardcoded secret ведёт к раскрытию метаданных или частичному обходу аутентификации, а у Dokploy - к CVSS 10.0, потому что SSH-терминал замыкает цепочку до полного RCE без промежуточных шагов.

Причина живучести этого класса - не в незнании, а в приоритетах. Разработчик добавляет fallback «чтобы проект запускался из коробки без конфигурации», и это действительно удобно. Проблема в том, что граница между dev-удобством и production-дырой стирается в момент, когда пользователь делает docker compose up на публичном VPS и забывает про переменные окружения. Ни один README не гарантирует, что все прочитают раздел «Security Configuration» перед деплоем. При аудите любой self-hosted платформы - Dokploy, Coolify, CapRover - первое действие: grep -r "secret" --include="[I].ts" --include="[/I].js" | grep -i "default\|fallback". Пять секунд - и понятно, стоит ли копать дальше. Если продукт не падает при старте без явно заданного секрета - значит где-то внутри лежит hardcoded-значение. И его уже кто-то знает.
 
Мы в соцсетях:

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

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

HackerLab