Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-68621 — Educational PoC and analysis of CVE-2025-68621, a timing attack on Trilium Notes sync login. Demonstrates HMAC hash recovery via network timing side-channel, with root cause, fix, and mitigation details. | Kitploit
Tools/GitHubGitHub/sivaadityacoder/cve-2025-68621
Vulnerability AnalysisExploitationWeb SecurityCryptographyPapers & ResearchLearning & Education
GitHubsivaadityacoder/cve-2025-68621

CVE-2025-68621

Educational PoC and analysis of CVE-2025-68621, a timing attack on Trilium Notes sync login. Demonstrates HMAC hash recovery via network timing side-channel, with root cause, fix, and mitigation details.

View Repository
55 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2025-68621 — Trilium Notes Timing Attack on /api/login/sync

Severity: HIGH (CVSS 7.4) Affected Software: TriliumNext/Trilium < 0.101.0 Vulnerability Type: CWE-208 – Observable Timing Discrepancy Fixed In: Trilium 0.101.0 (PR #8129) Published: 2026-02-06 | Reserved: 2025-12-19


Table of Contents

  1. My Approach
  2. Root Cause
  3. Impact
  4. Fix
  5. Key Takeaways
  6. Timeline
  7. References

My Approach

What Is Trilium Notes?

Trilium Notes is an open-source, cross-platform, hierarchical note-taking application designed for building large personal knowledge bases. It supports:

  • A self-hosted server that multiple clients can sync with
  • Rich note types (text, code, canvas, diagrams)
  • A powerful scripting API

The sync feature lets a Trilium client authenticate to a Trilium server so notes stay in sync across devices. This sync endpoint is the entry point for CVE-2025-68621.

What Is a Timing Attack?

A timing attack is a side-channel attack where an attacker learns secret information by measuring how long a system takes to process different inputs.

The classic example is string comparison:

"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

Most programming languages compare strings character by character and stop as soon as a mismatch is found (early exit). This means:

  • A guess that matches the first byte takes a tiny bit longer than one that mismatches immediately.
  • By sending thousands of guesses and averaging response times, an attacker can statistically determine which byte is correct — position by position — until the full secret is recovered.

The fix is to use a constant-time comparison function that always inspects every byte regardless of where a mismatch occurs.

How the Vulnerability Was Discovered

The vulnerability was discovered through manual code review of Trilium's authentication logic. The researcher examined the sync login flow in apps/server/src/routes/api/login.ts and noticed the following pattern in the loginSync() function (around line 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..." }];
}

The red flag is the use of JavaScript's built-in !== operator for comparing HMAC hashes. The !== operator is not constant-time — it exits as soon as it finds a differing character. Because the comparison is done on plain strings (not using a cryptographically safe comparison function), response time leaks information about how many leading bytes of the attacker's guess are correct.

The researcher then asked:

"Can this small timing difference be amplified enough, across a network, to recover the full 44-character Base64-encoded HMAC hash?"

The answer turned out to be yes — with enough repeated measurements and some statistical analysis, the signal rises above the noise.

The Attack Algorithm

When a Trilium client wants to sync, it calls POST /api/login/sync with a JSON body like:

{
  "timestamp":   "2025-12-19T10:00:00.000Z",
  "syncVersion": 34,
  "hash":        "<HMAC-SHA256 of documentSecret + timestamp, Base64-encoded>"
}

The byte-by-byte recovery works as follows:

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

After 44 iterations (one per Base64 character), the full 44-character HMAC hash is recovered.

Practical requirements:

  • >100 000 total HTTP requests (50 samples × 65 charset chars × 44 positions ≈ 143 000)
  • >1 000 different source IP addresses due to Trilium's rate-limiting (requires rotating proxies or a botnet)
  • Low network jitter between attacker and server (LAN or stable cloud connection works best)
  • A high-precision timer (time.perf_counter() in Python gives nanosecond resolution)

Proof of Concept

See poc.py for a fully annotated Python PoC.

Quick summary of what the PoC does:

  1. Iterates through all 44 Base64 character positions of the HMAC hash.
  2. For each position, tries every character in the Base64 charset (A–Z, a–z, 0–9, +, /, =).
  3. Sends 50 HTTP POST requests to /api/login/sync for each candidate and measures the median response time.
  4. Selects the candidate with the highest median response time as the correct character.
  5. After recovering all 44 characters, authenticates with the recovered hash.

Disclaimer: This PoC is provided for educational purposes and responsible security research only. Do not use against systems you do not own or have explicit written permission to test.


Root Cause

JavaScript's !== (and ===) operators perform a lexicographic, early-exit comparison. The vulnerable line in apps/server/src/routes/api/login.ts:

if (expectedHash !== givenHash) {
    return [400, { message: "Sync login credentials are incorrect..." }];
}

The early-exit behaviour creates a measurable timing difference per matching byte:

Guess vs. ExpectedBytes comparedTime
Wrong byte 01~T
Correct byte 0, wrong byte 12~T + δ
Correct bytes 0–1, wrong byte 23~T + 2δ
………
All 44 bytes correct44~T + 43δ

Each additional matching byte costs a tiny extra amount of CPU time δ. Over thousands of samples, the average response time for a "correct byte N" guess is measurably longer than for an "incorrect byte N" guess, leaking enough information to recover the full HMAC hash character by character.

CVSS Score Breakdown

MetricValueReason
Base Score7.4 HIGH
Attack VectorNetwork (N)Exploitable over the internet
Attack ComplexityHigh (H)Requires many requests + stable timing
Privileges RequiredNone (N)No account needed
User InteractionNone (N)Victim does not need to do anything
ScopeUnchanged (U)Only the Trilium server is affected
ConfidentialityHigh (H)Full note base is readable
IntegrityHigh (H)Attacker can write/modify notes
AvailabilityNone (N)No denial-of-service component

Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N


Impact

A successful exploit gives the attacker:

  • Complete read access to all notes, including encrypted note metadata
  • Complete write access — the attacker can create, modify, or delete notes
  • Persistent access — the recovered hash can be reused (within the timestamp window)

This is especially severe for users who store sensitive personal data (passwords, private documents, journal entries) in their Trilium knowledge base.


Fix

The fix replaces the non-constant-time !== comparison with Node.js's built-in crypto.timingSafeEqual():

Before (vulnerable):

if (expectedHash !== givenHash) {
    return [400, { message: "Sync login credentials are incorrect..." }];
}

After (secure):

import * as crypto from "crypto";
Download Tool