sivaadityacoder/CVE-2025-68621
GitHub: sivaadityacoder/CVE-2025-68621
针对 Trilium Notes 同步登录接口的计时攻击漏洞(CVE-2025-68621)的完整技术分析与概念验证,通过逐字节 HMAC 哈希恢复实现身份验证绕过。
Stars: 0 | Forks: 0
# CVE-2025-68621 — Trilium Notes `/api/login/sync` 上的计时攻击
## 目录
1. [我的方法](#my-approach)
2. [根本原因](#root-cause)
3. [影响](#impact)
4. [修复方案](#fix)
5. [核心要点](#key-takeaways)
6. [时间线](#timeline)
7. [参考资料](#references)
## 我的方法
### 什么是 Trilium Notes?
[Trilium Notes](https://github.com/TriliumNext/Trilium) 是一个开源、跨平台的层级式笔记应用程序,旨在构建大型个人知识库。它支持:
- 一个允许多个客户端同步的自托管服务器
- 丰富的笔记类型(文本、代码、画布、图表)
- 强大的脚本 API
**同步功能**允许 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
```
大多数编程语言逐个字符比较字符串,并在**发现不匹配时立即停止**(提前退出)。这意味着:
- 与第一个字节匹配的猜测比立即不匹配的猜测花费的时间*稍长*。
- 通过发送数千次猜测并对响应时间取平均值,攻击者可以统计性地确定哪个字节是正确的——逐个位置——直到恢复完整的秘密。
解决方法是使用**常量时间比较**函数,无论不匹配出现在何处,该函数都会检查每个字节。
### 漏洞是如何被发现的
该漏洞是通过对 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 哈希。`!==` 运算符**不是常量时间的**——它会在找到不同的字符时立即退出。由于比较是在普通字符串上进行的(未使用加密安全的比较函数),因此响应时间会泄露有关攻击者猜测中有多少前导字节是正确的信息。
研究人员随后提出:
答案是**肯定的**——通过足够的重复测量和一些统计分析,信号会从噪音中凸显出来。
### 攻击算法
当 Trilium 客户端想要同步时,它会使用如下 JSON 主体调用 `POST /api/login/sync`:
```
{
"timestamp": "2025-12-19T10:00:00.000Z",
"syncVersion": 34,
"hash": ""
}
```
逐字节的恢复过程如下:
```
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 哈希。
**实际要求:**
- **超过 100,000 次 HTTP 请求**(50 个样本 × 65 个字符集字符 × 44 个位置 ≈ 143,000)
- 由于 Trilium 的速率限制,需要**超过 1,000 个不同的源 IP 地址**(需要轮换代理或僵尸网络)
- 攻击者与服务器之间的**低网络抖动**(局域网或稳定的云连接效果最佳)
- 一个**高精度计时器**(Python 中的 `time.perf_counter()` 可提供纳秒级分辨率)
### 概念验证
请参阅 [`poc.py`](./poc.py) 获取带有完整注释的 Python PoC。
**PoC 功能简要概述:**
1. 遍历 HMAC 哈希的所有 44 个 Base64 字符位置。
2. 对于每个位置,尝试 Base64 字符集(`A–Z`、`a–z`、`0–9`、`+`、`/`、`=`)中的每个字符。
3. 为每个候选项发送 50 个 HTTP POST 请求到 `/api/login/sync`,并测量中位响应时间。
4. 选择具有最高中位响应时间的候选项作为正确字符。
5. 恢复所有 44 个字符后,使用恢复出的哈希进行身份验证。
## 根本原因
JavaScript 的 `!==`(和 `===`)运算符执行**字典序的提前退出比较**。`apps/server/src/routes/api/login.ts` 中的漏洞行:
```
if (expectedHash !== givenHash) {
return [400, { message: "Sync login credentials are incorrect..." }];
}
```
提前退出行为会为每个匹配字节产生可测量的时间差异:
| 猜测 vs 预期 | 比较字节数 | 耗时 |
|--------------------|----------------|------|
| 字节 0 错误 | 1 | ~T |
| 字节 0 正确,字节 1 错误 | 2 | ~T + δ |
| 字节 0–1 正确,字节 2 错误 | 3 | ~T + 2δ |
| … | … | … |
| 全部 44 字节正确 | 44 | ~T + 43δ |
每个额外的匹配字节都会消耗少量额外的 CPU 时间 *δ*。经过数千次采样,“字节 N 正确”猜测的平均响应时间在可测量程度上长于“字节 N 不正确”的猜测,从而泄露了足够的信息以逐个字符恢复完整的 HMAC 哈希。
### CVSS 评分细分
| 指标 | 值 | 原因 |
|--------|-------|--------|
| **基础分数** | **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`
## 影响
成功的利用可让攻击者获得:
- 对所有笔记的**完全读取权限**,包括加密笔记的元数据
- **完全写入权限**——攻击者可以创建、修改或删除笔记
- **持久访问权限**——恢复出的哈希可被重复使用(在时间戳窗口内)
对于在 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`](./vulnerable.ts) 和 [`fix.ts`](./fix.ts) 获取并排的代码示例。
### 如何更新
如果您正在运行自托管的 Trilium 服务器,请立即升级到 **0.101.0 或更高版本**。
```
# Docker 示例
docker pull zadam/trilium:0.101.0
```
## 核心要点
1. **切勿使用 `===` / `!==` 比较秘密信息。** JavaScript 的相等运算符不是常量时间的。任何使用 `===` / `!==` 进行的 HMAC、令牌或密码比较都可能成为计时预言机。
2. **在 Node.js 中务必始终使用 `crypto.timingSafeEqual()`**(或您所用语言/运行时中的等效方法)来比较加密值。这是为此任务量身定制的标准 API。
3. **网络上的计时攻击是真实存在的。** 虽然在网络中检测纳秒级差异似乎是不可能的,但统计技术和足够的样本可以从充满噪音的测量结果中提取出清晰的信号——尤其是在低抖动环境中。
4. **仅靠速率限制不足以缓解此问题。** 即使实施了针对每个 IP 的速率限制,有权访问轮换代理或僵尸网络的攻击者仍然可以积累足够的样本来利用计时差异。
5. **HMAC 验证应受到与密码比较同等的重视。** HMAC 哈希是秘密信息。请将任何对秘密值的比较视为可被利用的计时侧信道。
6. **对加密模式的代码审查至关重要。** 此漏洞是通过手动审查发现的——仅仅是一行看起来无害但具有严重安全隐患的代码。专门的加密/安全审计有助于及早发现这些问题。
## 时间线
| 日期 | 事件 |
|------|-------|
| 2025-12-19 | CVE-2025-68621 由 GitHub Security 预留 |
| 2025-12-21 | 修复 PR [#8129](https://github.com/TriliumNext/Trilium/pull/8129) 开启 |
| 2025-12-25 | PR 合并;Trilium 0.101.0 发布 |
| 2026-02-06 | CVE 公开发布 |
| 2026-02-09 | 添加 CISA ADP 扩充信息 |
## 参考资料
| 资源 | 链接 |
|----------|------|
| GitHub 安全公告 | [GHSA-hxf6-58cx-qq3x](https://github.com/TriliumNext/Trilium/security/advisories/GHSA-hxf6-58cx-qq3x) |
| 修复 Pull Request | [TriliumNext/Trilium#8129](https://github.com/TriliumNext/Trilium/pull/8129) |
| CVE 记录 (CVEProject) | [CVE-2025-68621.json](https://github.com/CVEProject/cvelistV5/blob/main/cves/2025/68xxx/CVE-2025-68621.json) |
| CWE-208 | [可观测的计时差异](https://cwe.mitre.org/data/definitions/208.html) |
| Trilium Notes 仓库 | [TriliumNext/Trilium](https://github.com/TriliumNext/Trilium) |
*本仓库基于负责任的披露原则,仅用于教育和研究目的进行维护。*
标签:CISA项目, CVE-2025-68621, GNU通用公共许可证, MITM代理, Node.js, Trilium Notes, Web安全, 侧信道攻击, 同步漏洞, 密码学, 恒定时间比较, 手动系统调用, 旁路攻击, 蓝队分析, 计时攻击, 身份验证绕过, 逆向工具, 逻辑漏洞