
Educational PoC et analyse de CVE-2025-68621, une attaque temporelle sur la connexion de synchronisation de Trilium Notes. Démontre la récupération du hachage HMAC via un canal auxiliaire temporel réseau, avec les détails de la cause racine, du correctif et de l'atténuation.
/api/login/syncSévérité : HAUTE (CVSS 7.4) Logiciel affecté : TriliumNext/Trilium < 0.101.0 Type de vulnérabilité : CWE-208 – Discrepance temporelle observable Corrigé dans : Trilium 0.101.0 (PR #8129) Publié : 2026-02-06 | Réservé : 2025-12-19
Trilium Notes est une application open-source, multiplateforme, de prise de notes hiérarchique conçue pour construire de grandes bases de connaissances personnelles. Elle prend en charge :
La fonctionnalité de synchronisation permet à un client Trilium de s'authentifier auprès d'un serveur Trilium afin que les notes restent synchronisées entre les appareils. Ce point de terminaison de synchronisation est la porte d'entrée de CVE-2025-68621.
Une attaque temporelle est une attaque par canal auxiliaire où un attaquant apprend une information secrète en mesurant combien de temps un système met à traiter différentes entrées.
L'exemple classique est la comparaison de chaînes :
"correct_password" !== "aorrect_password" → échec à la position 0 → rapide
"correct_password" !== "cXrrect_password" → échec à la position 1 → légèrement plus lent
"correct_password" !== "correct_password" → correspondance complète → le plus lent
La plupart des langages de programmation comparent les chaînes caractère par caractère et s'arrêtent dès qu'une différence est trouvée (sortie anticipée). Cela signifie :
La correction consiste à utiliser une fonction de comparaison en temps constant qui examine toujours chaque octet indépendamment de l'endroit où une différence se produit.
La vulnérabilité a été découverte par révision manuelle du code de la logique d'authentification de Trilium. Le chercheur a examiné le flux de connexion de synchronisation dans apps/server/src/routes/api/login.ts et a remarqué le motif suivant dans la fonction loginSync() (autour de la ligne 111) :
const documentSecret = options.getOption("documentSecret");
const expectedHash = utils.hmac(documentSecret, timestampStr);
const givenHash = req.body.hash;
if (expectedHash !== givenHash) { // ← LIGNE VULNÉRABLE
return [400, { message: "Sync login credentials are incorrect..." }];
}
Le signal d'alarme est l'utilisation de l'opérateur !== natif de JavaScript pour comparer des hachages HMAC. L'opérateur !== n'est pas en temps constant — il sort dès qu'il trouve un caractère différent. Comme la comparaison est effectuée sur des chaînes simples (sans utiliser de fonction de comparaison cryptographiquement sûre), le temps de réponse divulgue des informations sur le nombre d'octets de tête de la supposition de l'attaquant qui sont corrects.
Le chercheur s'est alors demandé :
"Cette petite différence temporelle peut-elle être suffisamment amplifiée, à travers un réseau, pour récupérer le hachage HMAC complet encodé en Base64 de 44 caractères ?"
La réponse s'est avérée être oui — avec suffisamment de mesures répétées et une analyse statistique, le signal dépasse le bruit.
Lorsqu'un client Trilium souhaite se synchroniser, il appelle POST /api/login/sync avec un corps JSON comme :
{
"timestamp": "2025-12-19T10:00:00.000Z",
"syncVersion": 34,
"hash": "<HMAC-SHA256 of documentSecret + timestamp, Base64-encoded>"
}
La récupération octet par octet fonctionne comme suit :
Pour position = 0 jusqu'à 43 :
Pour chaque caractère candidat c dans le jeu de caractères (A-Z, a-z, 0-9, +, /, =) :
Envoyer ÉCHANTILLONS requêtes avec hash = préfixe_connu + c + bourrage
Enregistrer le temps de réponse moyen
Meilleur caractère = candidat avec le temps moyen le plus élevé
Ajouter le meilleur caractère au préfixe_connu
Après 44 itérations (une par caractère Base64), le hachage HMAC complet de 44 caractères est récupéré.
Exigences pratiques :
time.perf_counter() en Python donne une résolution nanoseconde)Voir poc.py pour un PoC Python entièrement annoté.
Résumé rapide de ce que fait le PoC :
A–Z, a–z, 0–9, +, /, =)./api/login/sync pour chaque candidat et mesure le temps de réponse médian.Avertissement : Ce PoC est fourni à des fins éducatives et de recherche en sécurité responsable uniquement. Ne l'utilisez pas contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation écrite explicite de tester.
Les opérateurs !== (et ===) de JavaScript effectuent une comparaison lexicographique avec sortie anticipée. La ligne vulnérable dans apps/server/src/routes/api/login.ts :
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
Le comportement de sortie anticipée crée une différence temporelle mesurable par octet correspondant :
| Saisie vs. Attendu | Octets comparés | Temps |
|---|---|---|
| Mauvais octet 0 | 1 | ~T |
| Octet 0 correct, mauvais octet 1 | 2 | ~T + δ |
| Octets 0–1 corrects, mauvais octet 2 | 3 | ~T + 2δ |
| … | … | … |
| Les 44 octets corrects | 44 | ~T + 43δ |
Chaque octet supplémentaire correspondant coûte une minuscule quantité de temps CPU δ. Sur des milliers d'échantillons, le temps de réponse moyen pour une supposition "octet N correct" est mesurablement plus long que pour une supposition "octet N incorrect", divulguant suffisamment d'informations pour récupérer le hachage HMAC complet caractère par caractère.