
PoC educacional e análise do CVE-2025-68621, um ataque de temporização no login de sincronização do Trilium Notes. Demonstra a recuperação do hash HMAC por meio de canal lateral de temporização de rede, com causa raiz, correção e detalhes de mitigação.
/api/login/syncGravidade: ALTA (CVSS 7.4) Software Afetado: TriliumNext/Trilium < 0.101.0 Tipo de Vulnerabilidade: CWE-208 – Discrepância de Temporização Observável Corrigido em: Trilium 0.101.0 (PR #8129) Publicado: 2026-02-06 | Reservado: 2025-12-19
Trilium Notes é um aplicativo de anotações hierárquicas, open-source e multiplataforma, projetado para a construção de grandes bases de conhecimento pessoais. Ele oferece suporte a:
O recurso de sincronização permite que um cliente Trilium se autentique em um servidor Trilium para que as anotações permaneçam sincronizadas entre dispositivos. Esse endpoint de sincronização é o ponto de entrada da CVE-2025-68621.
Um ataque de temporização é um ataque de canal lateral em que o atacante obtém informações secretas medindo quanto tempo um sistema leva para processar diferentes entradas.
O exemplo clássico é a comparação de strings:
"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
A maioria das linguagens de programação compara strings caractere por caractere e para assim que encontra uma divergência (saída antecipada). Isso significa:
A correção consiste em usar uma função de comparação em tempo constante que sempre inspeciona todos os bytes, independentemente de onde ocorre uma divergência.
A vulnerabilidade foi descoberta por meio de revisão manual de código da lógica de autenticação do Trilium. O pesquisador examinou o fluxo de login de sincronização em apps/server/src/routes/api/login.ts e notou o seguinte padrão na função loginSync() (por volta da linha 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..." }];
}
O sinal de alerta é o uso do operador nativo !== do JavaScript para comparar hashes HMAC. O operador !== não é de tempo constante — ele interrompe a execução assim que encontra um caractere diferente. Como a comparação é feita com strings simples (sem usar uma função de comparação criptograficamente segura), o tempo de resposta vaza informações sobre quantos bytes iniciais da tentativa do atacante estão corretos.
O pesquisador então perguntou:
"Essa pequena diferença de temporização pode ser amplificada o suficiente, através de uma rede, para recuperar o hash HMAC completo de 44 caracteres codificado em Base64?"
A resposta acabou sendo sim — com medições repetidas suficientes e alguma análise estatística, o sinal supera o ruído.
Quando um cliente Trilium deseja sincronizar, ele chama POST /api/login/sync com um corpo JSON como:
{
"timestamp": "2025-12-19T10:00:00.000Z",
"syncVersion": 34,
"hash": "<HMAC-SHA256 of documentSecret + timestamp, Base64-encoded>"
}
A recuperação byte a byte funciona da seguinte forma:
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
Após 44 iterações (uma para cada caractere Base64), o hash HMAC completo de 44 caracteres é recuperado.
Requisitos práticos:
time.perf_counter() em Python oferece resolução de nanossegundos)Consulte poc.py para obter um PoC Python totalmente comentado.
Resumo rápido do que o PoC faz:
A–Z, a–z, 0–9, +, /, =)./api/login/sync para cada candidato e mede o tempo de resposta mediano.Aviso: Este PoC é fornecido apenas para fins educacionais e para pesquisa de segurança responsável. Não o utilize contra sistemas que você não possui ou para os quais não tenha autorização explícita por escrito para testar.
Os operadores !== (e ===) do JavaScript realizam uma comparação lexicográfica com saída antecipada. A linha vulnerável em apps/server/src/routes/api/login.ts:
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
O comportamento de saída antecipada cria uma diferença de temporização mensurável para cada byte correspondente:
| Tentativa vs. Esperado | Bytes comparados | Tempo |
|---|---|---|
| Byte 0 errado | 1 | ~T |
| Byte 0 correto, byte 1 errado | 2 | ~T + δ |
| Bytes 0–1 corretos, byte 2 errado | 3 | ~T + 2δ |
| … | … | … |
| Todos os 44 bytes corretos | 44 | ~T + 43δ |
Cada byte correspondente adicional custa uma quantidade mínima extra de tempo de CPU δ. Em milhares de amostras, o tempo de resposta médio para uma tentativa com "byte N correto" é mensuravelmente maior do que para uma tentativa com "byte N incorreto", vazando informações suficientes para recuperar o hash HMAC completo caractere por caractere.
| Métrica | Valor | Motivo |
|---|---|---|
| Pontuação Base | 7.4 ALTA | |
| Vetor de Ataque | Rede (N) | Explorável pela internet |
| Complexidade do Ataque | Alta (H) | Exige muitas solicitações + temporização estável |
| Privilégios Necessários | Nenhum (N) | Não é necessária conta |
| Interação do Usuário | Nenhuma (N) | A vítima não precisa fazer nada |
| Escopo | Inalterado (U) | Apenas o servidor Trilium é afetado |
| Confidencialidade | Alta (H) | Toda a base de anotações é legível |
| Integridade | Alta (H) | O atacante pode escrever/alterar anotações |
| Disponibilidade | Nenhuma (N) | Sem componente de negação de serviço |
String do Vetor: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Uma exploração bem-sucedida concede ao atacante: