
/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..." }];
}
Поведение с ранним выходом создаёт измеримую разницу во времени для каждого совпадающего байта:
Каждый дополнительный совпадающий байт требует крошечного дополнительного количества процессорного времени δ. На тысячах выборок среднее время ответа для догадки «верный байт N» измеримо больше, чем для догадки «неверный байт N», что раскрывает достаточно информации для восстановления полного HMAC-хеша посимвольно.
Строка вектора: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Успешная эксплуатация даёт злоумышленнику:
Это особенно серьёзно для пользователей, которые хранят чувствительные личные данные (пароли, личные документы, дневниковые записи) в своей базе знаний Trilium.
Исправление заменяет сравнение через !==, не выполняющееся за постоянное время, встроенной функцией Node.js crypto.timingSafeEqual():
До (уязвимо):
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
После (безопасно):
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() всегда сравнивает каждый байт, поэтому время выполнения не зависит от того, сколько байтов совпало. Тайминг-сигнал исчезает.
См. vulnerable.ts и fix.ts — примеры кода рядом для сравнения.
Если вы запускаете собственный (self-hosted) сервер Trilium, немедленно обновитесь до версии 0.101.0 или новее.
# Docker example
docker pull zadam/trilium:0.101.0
Никогда не используйте === / !== для сравнения секретов. Операторы равенства в JavaScript не выполняются за постоянное время. Любое сравнение HMAC-хешей, токенов или паролей с помощью === / !== — это потенциальный тайминг-оракул.
Всегда используйте crypto.timingSafeEqual() в Node.js (или эквивалент в вашем языке/рантайме) при сравнении криптографических значений. Это стандартный API, созданный специально для этой задачи.
Тайминг-атаки реально работают через сеть. Хотя наносекундные различия кажутся невозможными для обнаружения через интернет, статистические методы и достаточное количество выборок позволяют извлечь чёткий сигнал из зашумлённых измерений — особенно в средах с низким джиттером.
Одного ограничения частоты запросов недостаточно. Даже с ограничением частоты на IP злоумышленник, имеющий доступ к ротации прокси или ботнету, всё равно может накопить достаточно выборок для эксплуатации тайминг-разницы.
Проверка HMAC заслуживает такого же внимания, как сравнение паролей. HMAC-хеши — это секреты. Относитесь к любому сравнению секретного значения так, как будто тайминг-побочные каналы могут быть использованы.
Анализ кода на предмет криптографических паттернов обязателен. Эта уязвимость была найдена при ручном анализе — одна строка кода, которая выглядела безобидно, но имела серьёзные последствия для безопасности. Специализированные криптографические аудиты и аудиты безопасности помогают выявлять такие проблемы на ранних стадиях.
| Дата | Событие |
|---|
Этот репозиторий поддерживается в образовательных и исследовательских целях в соответствии с принципами ответственного раскрытия информации.
| Догадка против ожидаемого | Сравнено байт | Время |
|---|
| Неверный байт 0 | 1 | ~T |
| Верный байт 0, неверный байт 1 | 2 | ~T + δ |
| Верные байты 0–1, неверный байт 2 | 3 | ~T + 2δ |
| … | … | … |
| Все 44 байта верны | 44 | ~T + 43δ |
| Метрика | Значение | Причина |
|---|
| Базовый балл | 7.4 ВЫСОКИЙ | |
| Вектор атаки | Сетевой (N) | Эксплуатируется через интернет |
| Сложность атаки | Высокая (H) | Требует множества запросов и стабильного тайминга |
| Требуемые привилегии | Отсутствуют (N) | Учётная запись не нужна |
| Взаимодействие с пользователем | Отсутствует (N) | Жертве не нужно ничего делать |
| Область воздействия | Без изменений (U) | Затронут только сервер Trilium |
| Конфиденциальность | Высокая (H) | Вся база заметок доступна для чтения |
| Целостность | Высокая (H) | Злоумышленник может записывать/изменять заметки |
| Доступность | Отсутствует (N) | Компонента отказа в обслуживании нет |
| 2025-12-19 | CVE-2025-68621 зарезервирован GitHub Security |
| 2025-12-21 | Открыт исправляющий PR #8129 |
| 2025-12-25 | PR объединён; выпущена Trilium 0.101.0 |
| 2026-02-06 | CVE опубликован публично |
| 2026-02-09 | Добавлено обогащение от CISA ADP |
| Ресурс | Ссылка |
|---|
| Рекомендация GitHub Security | GHSA-hxf6-58cx-qq3x |
| Исправляющий pull request | TriliumNext/Trilium#8129 |
| Запись CVE (CVEProject) | CVE-2025-68621.json |
| CWE-208 | Наблюдаемое расхождение во времени (Observable Timing Discrepancy) |
| Репозиторий Trilium Notes | TriliumNext/Trilium |