
# Образовательный PoC и анализ CVE-2025-68621 — атака по времени на синхронизацию входа в Trilium Notes Демонстрирует восстановление HMAC-хэша через сетевой побочный канал по времени, с описанием первопричины, исправления и мер по смягчению.
/api/login/syncСерьёзность: ВЫСОКАЯ (CVSS 7.4) Затронутое ПО: TriliumNext/Trilium < 0.101.0 Тип уязвимости: CWE-208 – Наблюдаемое расхождение во времени (Observable Timing Discrepancy) Исправлено в: Trilium 0.101.0 (PR #8129) Опубликовано: 2026-02-06 | Зарезервировано: 2025-12-19
Trilium Notes — это приложение с открытым исходным кодом для ведения иерархических заметок, работающее на разных платформах и предназначенное для построения больших личных баз знаний. Оно поддерживает:
Функция синхронизации позволяет клиенту Trilium проходить аутентификацию на сервере Trilium, чтобы заметки оставались синхронизированными между устройствами. Эта конечная точка синхронизации — точка входа для CVE-2025-68621.
Тайминг-атака — это атака по побочным каналам, при которой злоумышленник узнаёт секретную информацию, измеряя, сколько времени система тратит на обработку различных входных данных.
Классический пример — сравнение строк:
"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
Большинство языков программирования сравнивают строки посимвольно и останавливаются, как только найдено несовпадение (ранний выход). Это означает:
Исправление заключается в использовании функции сравнения за постоянное время (constant-time comparison), которая всегда проверяет каждый байт, независимо от того, где произошло несовпадение.
Уязвимость была обнаружена в ходе ручного анализа кода логики аутентификации Trilium. Исследователь изучил процесс входа через синхронизацию в apps/server/src/routes/api/login.ts и заметил следующий фрагмент в функции loginSync() (примерно строка 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..." }];
}
Красный флаг — использование встроенного оператора JavaScript !== для сравнения HMAC-хешей. Оператор !== не выполняется за постоянное время — он завершается, как только находит отличающийся символ. Поскольку сравнение выполняется над обычными строками (без использования криптографически безопасной функции сравнения), время ответа раскрывает информацию о том, сколько начальных байтов догадки злоумышленника верны.
Затем исследователь задался вопросом:
«Можно ли усилить эту небольшую разницу во времени настолько, чтобы по сети восстановить полный 44-символьный HMAC-хеш в кодировке Base64?»
Ответ оказался да — при достаточном количестве повторных измерений и некотором статистическом анализе сигнал поднимается над шумом.
Когда клиент Trilium хочет выполнить синхронизацию, он вызывает POST /api/login/sync с телом JSON вида:
{
"timestamp": "2025-12-19T10:00:00.000Z",
"syncVersion": 34,
"hash": "<HMAC-SHA256 of documentSecret + timestamp, Base64-encoded>"
}
Восстановление байт за байтом работает следующим образом:
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
После 44 итераций (по одной на каждый символ Base64) восстанавливается полный 44-символьный HMAC-хеш.
Практические требования:
time.perf_counter() в Python обеспечивает разрешение в наносекундах)См. poc.py — полностью аннотированный Python-PoC.
Краткое описание того, что делает PoC:
A–Z, a–z, 0–9, +, /, =)./api/login/sync для каждого кандидата и измеряет медианное время ответа.Отказ от ответственности: Этот PoC предоставлен исключительно в образовательных целях и для ответственных исследований в области безопасности. Не используйте его против систем, которыми вы не владеете или на тестирование которых у вас нет явного письменного разрешения.
Операторы JavaScript !== (и ===) выполняют лексикографическое сравнение с ранним выходом. Уязвимая строка в apps/server/src/routes/api/login.ts:
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
Поведение с ранним выходом создаёт измеримую разницу во времени для каждого совпадающего байта:
| Догадка против ожидаемого | Сравнено байт | Время |
|---|---|---|
| Неверный байт 0 | 1 | ~T |
| Верный байт 0, неверный байт 1 | 2 | ~T + δ |
| Верные байты 0–1, неверный байт 2 | 3 | ~T + 2δ |
| … | … | … |
| Все 44 байта верны | 44 | ~T + 43δ |
Каждый дополнительный совпадающий байт требует крошечного дополнительного количества процессорного времени δ. На тысячах выборок среднее время ответа для догадки «верный байт N» измеримо больше, чем для догадки «неверный байт N», что раскрывает достаточно информации для восстановления полного HMAC-хеша посимвольно.
| Метрика | Значение | Причина |
|---|---|---|
| Базовый балл | 7.4 ВЫСОКИЙ | |
| Вектор атаки | Сетевой (N) | Эксплуатируется через интернет |
| Сложность атаки | Высокая (H) | Требует множества запросов и стабильного тайминга |
| Требуемые привилегии | Отсутствуют (N) | Учётная запись не нужна |
| Взаимодействие с пользователем | Отсутствует (N) | Жертве не нужно ничего делать |
| Область воздействия | Без изменений (U) | Затронут только сервер Trilium |
| Конфиденциальность | Высокая (H) | Вся база заметок доступна для чтения |
| Целостность | Высокая (H) | Злоумышленник может записывать/изменять заметки |
| Доступность | Отсутствует (N) | Компонента отказа в обслуживании нет |
Строка вектора: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Успешная эксплуатация даёт злоумышленнику: