
/api/login/syncにおけるタイミング攻撃重大度: 高 (CVSS 7.4) 影響を受けるソフトウェア: TriliumNext/Trilium < 0.101.0 脆弱性タイプ: CWE-208 – 観察可能なタイミング不整合 修正バージョン: 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
ほとんどのプログラミング言語は文字列を文字単位で比較し、不一致が見つかった時点で停止します(早期終了)。これは以下のことを意味します:
修正方法は、不一致が発生する場所に関係なく常にすべてのバイトを検査する定数時間比較関数を使用することです。
この脆弱性は、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..." }];
}
危険信号は、HMACハッシュの比較にJavaScriptの組み込みの!==演算子を使用していることです。!==演算子は定数時間ではありません — 異なる文字を見つけるとすぐに終了します。比較が(暗号学的に安全な比較関数ではなく)プレーンな文字列で行われるため、応答時間は攻撃者の推測の先頭の何バイトが正しいかに関する情報を漏洩します。
研究者はその後、次のように問いました:
「このわずかな時間差は、ネットワーク越しに十分に増幅して、44文字のBase64エンコードされたHMACハッシュ全体を復元できるだろうか?」
その答えはイエスであることが判明しました — 十分な反復測定といくらかの統計分析により、シグナルはノイズを上回ります。
Triliumクライアントが同期したい場合、以下のようなJSONボディでPOST /api/login/syncを呼び出します:
{
"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文字ごとに1回)の後、44文字のHMACハッシュ全体が復元されます。
実際的な要件:
time.perf_counter()はナノ秒分解能を提供)完全に注釈付きのPython PoCについてはpoc.pyを参照してください。
PoCが行うことの簡単な概要:
A–Z、a–z、0–9、+、/、=)のすべての文字を試します。/api/login/syncに50件のHTTP POSTリクエストを送信し、応答時間の中央値を測定します。免責事項: この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δ |
一致するバイトが1つ増えるごとに、わずかな追加のCPU時間δがかかります。何千ものサンプルにわたって、「バイトNが正しい」推測の平均応答時間は「バイトNが誤り」の推測よりも測定可能なほど長くなり、HMACハッシュ全体を文字単位で復元するのに十分な情報が漏洩します。
| メトリクス | 値 | 理由 |
|---|---|---|
| 基本スコア | 7.4 高 | |
| 攻撃元 (Attack Vector) | ネットワーク (N) | インターネット経由で悪用可能 |
| 攻撃の複雑さ (Attack Complexity) | 高 (H) | 多数のリクエスト+安定したタイミングが必要 |
| 必要な特権 (Privileges Required) | なし (N) | アカウントは不要 |
| ユーザー操作 (User Interaction) | なし (N) | 被害者が何かをする必要はない |
| スコープ (Scope) | 変更なし (U) | 影響を受けるのはTriliumサーバーのみ |
| 機密性 (Confidentiality) | 高 (H) | ノートベース全体が読み取り可能 |
| 完全性 (Integrity) | 高 (H) | 攻撃者はノートの書き込み・変更が可能 |
| 可用性 (Availability) | なし (N) | DoS(サービス拒否)要素なし |
ベクター文字列: 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を参照してください。
セルフホスト型Triliumサーバーを実行している場合は、直ちにバージョン0.101.0以降にアップグレードしてください。
# Docker example
docker pull zadam/trilium:0.101.0
秘密の比較に=== / !==を使用しないでください。 JavaScriptの等価演算子は定数時間ではありません。=== / !==を使用したHMAC、トークン、またはパスワードの比較はすべて、潜在的なタイミングオラクルです。
暗号化値を比較するときは、Node.jsで常にcrypto.timingSafeEqual()を使用してください(または使用言語・ランタイムの同等のもの)。これはこのタスク用の標準的で目的に特化したAPIです。
タイミング攻撃はネットワーク越しでも現実的です。 ナノ秒単位の差はインターネット越しに検出するのは不可能に思えますが、統計的手法と十分なサンプル数があれば、ノイズの多い測定値から明確なシグナルを抽出できます — 特にジッタの少ない環境では。
レート制限だけでは十分な緩和策になりません。 IPごとのレート制限があっても、ローテーションプロキシやボットネットにアクセスできる攻撃者は、タイミングの差を悪用するのに十分なサンプルを蓄積できます。
HMACの検証は、パスワード比較と同じ注意を払う価値があります。 HMACハッシュは秘密です。秘密値の比較はすべて、タイミングサイドチャネルが悪用される可能性があるものとして扱ってください。
暗号化パターンのコードレビューは不可欠です。 この脆弱性は手動レビューで発見されました — 一見無害に見えるが重大なセキュリティ影響を持つ1行のコードでした。専任の暗号・セキュリティ監査は、これらの問題を早期に発見するのに役立ちます。
| 日付 | 出来事 |
|---|---|
| 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 Advisory | GHSA-hxf6-58cx-qq3x |
| 修正プルリクエスト | TriliumNext/Trilium#8129 |
| CVEレコード (CVEProject) | CVE-2025-68621.json |
| CWE-208 | 観察可能なタイミング不整合 |
| Trilium Notesリポジトリ | TriliumNext/Trilium |
このリポジトリは、責任ある開示の原則に基づき、教育および研究目的で保守されています。