
/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:
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.
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:
Isso é especialmente grave para usuários que armazenam dados pessoais sensíveis (senhas, documentos privados, anotações de diário) em sua base de conhecimento do Trilium.
A correção substitui a comparação !== sem tempo constante pela função nativa crypto.timingSafeEqual() do Node.js:
Antes (vulnerável):
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
Depois (seguro):
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() sempre compara todos os bytes, portanto o tempo de execução não depende de quantos bytes correspondem. O sinal de temporização desaparece.
Consulte vulnerable.ts e fix.ts para exemplos de código lado a lado.
Se você estiver executando um servidor Trilium auto-hospedado, atualize imediatamente para a versão 0.101.0 ou posterior.
# Docker example
docker pull zadam/trilium:0.101.0
Nunca use === / !== para comparar segredos. Os operadores de igualdade do JavaScript não são de tempo constante. Qualquer comparação de HMACs, tokens ou senhas usando === / !== é um potencial oráculo de temporização.
Sempre use crypto.timingSafeEqual() no Node.js (ou um equivalente em sua linguagem/runtime) ao comparar valores criptográficos. Esta é a API padrão e específica para essa tarefa.
Ataques de temporização são reais através da rede. Embora diferenças de nanossegundos pareçam impossíveis de detectar pela internet, técnicas estatísticas e amostras suficientes podem extrair um sinal claro de medições ruidosas — especialmente em ambientes com baixo jitter.
A limitação de taxa por si só não é uma mitigação suficiente. Mesmo com limitação de taxa por IP, um atacante com acesso a proxies rotativos ou a uma botnet ainda pode acumular amostras suficientes para explorar a diferença de temporização.
A verificação de HMAC merece o mesmo cuidado que a comparação de senhas. Hashes HMAC são segredos. Trate qualquer comparação de um valor secreto como se canais laterais de temporização pudessem ser explorados.
A revisão de código para padrões criptográficos é essencial. Esta vulnerabilidade foi encontrada por meio de revisão manual — uma única linha de código que parecia inofensiva, mas tinha sérias implicações de segurança. Auditorias dedicadas de criptografia/segurança ajudam a identificar esses problemas cedo.
Este repositório é mantido para fins educacionais e de pesquisa, de acordo com os princípios de divulgação responsável.
| 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δ |
| 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 |
| Data | Evento |
|---|
| 2025-12-19 | CVE-2025-68621 reservada pela Segurança do GitHub |
| 2025-12-21 | PR de correção #8129 aberto |
| 2025-12-25 | PR mesclado; Trilium 0.101.0 lançado |
| 2026-02-06 | CVE publicada publicamente |
| 2026-02-09 | Enriquecimento ADP da CISA adicionado |
| Recurso | Link |
|---|
| Aviso de Segurança do GitHub | GHSA-hxf6-58cx-qq3x |
| Pull Request da Correção | TriliumNext/Trilium#8129 |
| Registro CVE (CVEProject) | CVE-2025-68621.json |
| CWE-208 | Discrepância de Temporização Observável |
| Repositório do Trilium Notes | TriliumNext/Trilium |