Сергей Попов
Администратор
- 30.12.2015
- 6 217
- 6 957
- Специализация
- OSINT
- Веб-безопасность
- Статус верификации
- ✓ Verified
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-сегменте
Техническая анатомия 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 на хосте
Атака разворачивается в четыре шага:- Получение секрета - строка
better-auth-secret-123456789берётся из публичного репозитория Dokploy - Форжинг JWT - атакующий создаёт поддельный JWT для верификации email, указывая email существующего администратора
- Auto-sign-in - при успешной верификации Dokploy автоматически создаёт сессию с правами пользователя, чей email был «подтверждён». Атакующий получает администраторскую сессию без ввода пароля
- RCE через SSH-терминал - встроенный SSH-терминал в веб-интерфейсе Dokploy позволяет администратору выполнять команды на хосте. Атакующий с админ-сессией просто открывает терминал и делает что хочет
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H подтверждает каждый аспект цепочки:| Компонент | Значение | Что означает |
|---|---|---|
| AV:N | Network | Атака по сети |
| AC:L | Low | Секрет публичен, сложность минимальна |
| PR:N | None | Привилегии не требуются |
| UI:N | None | Действие пользователя не нужно |
| S:C | Changed | Выход за границу приложения на хост |
| C:H / I:H / A:H | High | Полная компрометация |
S:C (Scope: Changed) - тут самое интересное: уязвимость в веб-приложении позволяет атакующему выйти за его пределы и получить контроль над хост-системой.
Место в kill chain: от initial access до контроля хоста
В терминологии MITRE ATT&CK цепочка эксплуатации CVE-2026-45631 покрывает несколько тактик:| Этап | Техника ATT&CK | Описание |
|---|---|---|
| Initial Access | Exploit Public-Facing Application (T1190) | Эксплуатация публично доступной панели Dokploy |
| Credential Access | Credentials In Files (T1552.001) | Hardcoded-секрет в исходном коде |
| Credential Access | Forge Web Credentials (T1606) | Создание поддельного JWT |
| Privilege Escalation | Valid Accounts (T1078) | Использование администраторской сессии |
| Execution | Deploy 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)
/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
- Определить текущую версию: если ниже 0.29.3 - обновлять немедленно
- Обновить Dokploy до 0.29.3 или выше (исправление в PR #4374)
- Явно задать BETTER_AUTH_SECRET:
openssl rand -base64 32- и прописать в переменные окружения контейнера (минимум 32 символа) - Проверить логи на обращения к
/api/auth/verify-emailот IP, не принадлежащих легитимным пользователям - Проверить список администраторов в панели - не появились ли незнакомые аккаунты
- Ограничить сетевой доступ к панели: VPN, IP-whitelist на firewall, или Cloudflare Access / Authelia перед Dokploy
- Аудит SSH-ключей на хосте:
cat ~/.ssh/authorized_keys- убедиться в отсутствии чужих ключей - Проверить crontab и systemd-юниты на хосте на предмет бэкдоров
- Ротировать все секреты (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 часто угадываем, так что ограничение скорее теоретическое
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-значение. И его уже кто-то знает.