
/api/login/syncGravità: ALTA (CVSS 7.4) Software interessato: TriliumNext/Trilium < 0.101.0 Tipo di vulnerabilità: CWE-208 – Discrepanza temporale osservabile Corretta in: Trilium 0.101.0 (PR #8129) Pubblicata: 2026-02-06 | Riservata: 2025-12-19
Trilium Notes è un'applicazione open-source, multipiattaforma e gerarchica per prendere appunti, progettata per costruire grandi basi di conoscenza personali. Supporta:
La funzionalità di sincronizzazione consente a un client Trilium di autenticarsi su un server Trilium così che le note rimangano sincronizzate tra dispositivi. Questo endpoint di sincronizzazione è il punto di ingresso per CVE-2025-68621.
Un attacco timing è un attacco side-channel in cui un attaccante apprende informazioni segrete misurando quanto tempo impiega un sistema per elaborare input diversi.
L'esempio classico è il confronto tra stringhe:
"correct_password" !== "aorrect_password" → fails at position 0 → fast
"correct_password" !== "cXrrect_password" → fails at position 1 → slightly slower
"correct_password" !== "correct_password" → matches fully → slowest
La maggior parte dei linguaggi di programmazione confronta le stringhe carattere per carattere e si ferma appena trova una discrepanza (uscita anticipata). Questo significa:
La soluzione è usare una funzione di confronto a tempo costante che ispezioni sempre ogni byte, indipendentemente da dove si verifica una discrepanza.
La vulnerabilità è stata scoperta tramite revisione manuale del codice della logica di autenticazione di Trilium. Il ricercatore ha esaminato il flusso di login di sincronizzazione in apps/server/src/routes/api/login.ts e ha notato il seguente schema nella funzione loginSync() (intorno alla riga 111):
const documentSecret = options.getOption("documentSecret");
const expectedHash = utils.hmac(documentSecret, timestampStr);
const givenHash = req.body.hash;
if (expectedHash !== givenHash) { // ← VULNERABLE LINE
return [400, { message: "Sync login credentials are incorrect..." }];
}
Il campanello d'allarme è l'uso dell'operatore !== integrato di JavaScript per confrontare gli hash HMAC. L'operatore !== non è a tempo costante — esce appena trova un carattere diverso. Poiché il confronto avviene su stringhe semplici (senza usare una funzione di confronto crittograficamente sicura), il tempo di risposta rivela informazioni su quanti byte iniziali dell'ipotesi dell'attaccante sono corretti.
Il ricercatore si è quindi chiesto:
"Questa piccola differenza di temporizzazione può essere amplificata abbastanza, attraverso una rete, da recuperare l'intero hash HMAC di 44 caratteri codificato in Base64?"
La risposta si è rivelata sì — con abbastanza misurazioni ripetute e un po' di analisi statistica, il segnale supera il rumore.
Quando un client Trilium vuole sincronizzarsi, chiama POST /api/login/sync con un body JSON come il seguente:
{
"timestamp": "2025-12-19T10:00:00.000Z",
"syncVersion": 34,
"hash": "<HMAC-SHA256 of documentSecret + timestamp, Base64-encoded>"
}
Il recupero byte per byte funziona come segue:
For position = 0 to 43:
For each candidate character c in charset (A-Z, a-z, 0-9, +, /, =):
Send SAMPLES requests with hash = known_prefix + c + padding
Record average response time
Best character = candidate with highest average time
Append best character to known_prefix
Dopo 44 iterazioni (una per carattere Base64), viene recuperato l'intero hash HMAC di 44 caratteri.
Requisiti pratici:
time.perf_counter() in Python offre una risoluzione in nanosecondi)Vedi poc.py per un PoC Python completamente annotato.
Breve riepilogo di ciò che fa il PoC:
A–Z, a–z, 0–9, +, /, =)./api/login/sync per ogni candidato e misura la mediana del tempo di risposta.Disclaimer: Questo PoC è fornito esclusivamente a scopo didattico e per ricerca sulla sicurezza responsabile. Non utilizzarlo contro sistemi che non possiedi o per i quali non hai esplicita autorizzazione scritta a testare.
Gli operatori !== (e ===) di JavaScript eseguono un confronto lessicografico con uscita anticipata. La riga vulnerabile in apps/server/src/routes/api/login.ts:
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
Il comportamento di uscita anticipata crea una differenza di temporizzazione misurabile per ogni byte corrispondente:
Ogni byte corrispondente aggiuntivo costa una piccolissima quantità extra di tempo CPU δ. Su migliaia di campioni, il tempo di risposta medio per un'ipotesi "byte N corretto" è misurabilmente più lungo di quello per un'ipotesi "byte N errato", rivelando abbastanza informazioni da recuperare l'intero hash HMAC carattere per carattere.
Stringa del vettore: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Un exploit riuscito garantisce all'attaccante:
Ciò è particolarmente grave per gli utenti che conservano dati personali sensibili (password, documenti privati, voci di diario) nella propria base di conoscenza Trilium.
La correzione sostituisce il confronto !== non a tempo costante con la funzione integrata crypto.timingSafeEqual() di Node.js:
Prima (vulnerabile):
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
Dopo (sicuro):
import * as crypto from "crypto";
const expectedBuffer = Buffer.from(expectedHash);
const givenBuffer = Buffer.from(givenHash ?? "");
if (expectedBuffer.length !== givenBuffer.length ||
!crypto.timingSafeEqual(expectedBuffer, givenBuffer)) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
crypto.timingSafeEqual() confronta sempre ogni byte, quindi il tempo di esecuzione non dipende da quanti byte corrispondono. Il segnale di temporizzazione scompare.
Vedi vulnerable.ts e fix.ts per esempi di codice affiancati.
Se stai eseguendo un server Trilium self-hosted, aggiorna immediatamente alla versione 0.101.0 o successiva.
# Docker example
docker pull zadam/trilium:0.101.0
Non usare mai === / !== per confrontare segreti. Gli operatori di uguaglianza di JavaScript non sono a tempo costante. Qualsiasi confronto di HMAC, token o password che usa === / !== è una potenziale oracle di temporizzazione.
Usa sempre crypto.timingSafeEqual() in Node.js (o un equivalente nel tuo linguaggio/runtime) quando confronti valori crittografici. È l'API standard e progettata appositamente per questo compito.
Gli attacchi timing sono reali attraverso la rete. Sebbene le differenze in nanosecondi sembrino impossibili da rilevare su internet, le tecniche statistiche e un numero sufficiente di campioni possono estrarre un segnale chiaro da misurazioni rumorose — soprattutto in ambienti a basso jitter.
Il rate-limiting da solo non è una mitigazione sufficiente. Anche con rate-limiting per IP, un attaccante con accesso a proxy rotanti o a una botnet può comunque accumulare abbastanza campioni per sfruttare la differenza di temporizzazione.
La verifica HMAC merita la stessa attenzione del confronto delle password. Gli hash HMAC sono segreti. Tratta qualsiasi confronto di un valore segreto come se i canali laterali di temporizzazione potessero essere sfruttati.
La revisione del codice per i pattern crittografici è essenziale. Questa vulnerabilità è stata trovata tramite revisione manuale — una singola riga di codice che sembrava innocua ma aveva gravi implicazioni per la sicurezza. Audit dedicati di crittografia/sicurezza aiutano a individuare questi problemi in anticipo.
| Data |
|---|
Questo repository è mantenuto per scopi educativi e di ricerca secondo i principi della divulgazione responsabile.
| Ipotesi vs. atteso | Byte confrontati | Tempo |
|---|
| Byte 0 errato | 1 | ~T |
| Byte 0 corretto, byte 1 errato | 2 | ~T + δ |
| Byte 0–1 corretti, byte 2 errato | 3 | ~T + 2δ |
| … | … | … |
| Tutti i 44 byte corretti | 44 | ~T + 43δ |
| Metrica | Valore | Motivo |
|---|
| Punteggio base | 7.4 ALTO | |
| Vettore di attacco | Rete (N) | Sfruttabile via internet |
| Complessità di attacco | Alta (H) | Richiede molte richieste + timing stabile |
| Privilegi richiesti | Nessuno (N) | Nessun account necessario |
| Interazione dell'utente | Nessuna (N) | La vittima non deve fare nulla |
| Ambito | Invariato (U) | È interessato solo il server Trilium |
| Riservatezza | Alta (H) | L'intera base di note è leggibile |
| Integrità | Alta (H) | L'attaccante può scrivere/modificare le note |
| Disponibilità | Nessuna (N) | Nessuna componente di denial-of-service |
| Evento |
|---|
| 2025-12-19 | CVE-2025-68621 riservata da GitHub Security |
| 2025-12-21 | Aperta la PR di correzione #8129 |
| 2025-12-25 | PR unita; rilasciata Trilium 0.101.0 |
| 2026-02-06 | CVE pubblicata pubblicamente |
| 2026-02-09 | Aggiunto arricchimento ADP CISA |
| Risorsa | Link |
|---|
| Advisory di sicurezza GitHub | GHSA-hxf6-58cx-qq3x |
| Pull Request di correzione | TriliumNext/Trilium#8129 |
| Record CVE (CVEProject) | CVE-2025-68621.json |
| CWE-208 | Discrepanza di temporizzazione osservabile |
| Repository di Trilium Notes | TriliumNext/Trilium |