
إثبات مفهوم تعليمي وتحليل لـ CVE-2025-68621، هجوم توقيت على تسجيل دخول المزامنة في Trilium Notes. يوضح استرداد تجزئة HMAC عبر القناة الجانبية لتوقيت الشبكة، مع السبب الجذري والإصلاح وتفاصيل التخفيف.
/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" → يفشل في الموضع 0 → سريع
"correct_password" !== "cXrrect_password" → يفشل في الموضع 1 → أبطأ قليلاً
"correct_password" !== "correct_password" → تطابق كامل → أبطأ
تقارن معظم لغات البرمجة السلاسل النصية حرفًا بحرف وتتوقف فور اكتشاف عدم تطابق (خروج مبكر). يعني هذا:
الحل هو استخدام دالة مقارنة زمنية ثابتة تفحص دائمًا كل بايت بغض النظر عن مكان حدوث عدم التطابق.
تم اكتشاف الثغرة من خلال مراجعة يدوية للكود لمنطق المصادقة في 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) { // ← سطر ضعيف
return [400, { message: "Sync login credentials are incorrect..." }];
}
العلم الأحمر هو استخدام عامل المقارنة المدمج !== في JavaScript لمقارنة تجزئات HMAC. عامل !== ليس زمنيًا ثابتًا — فهو يخرج فور العثور على حرف مختلف. نظرًا لأن المقارنة تتم على سلاسل نصية عادية (بدون استخدام دالة مقارنة آمنة تشفيريًا)، فإن وقت الاستجابة يتسرب معلومات حول عدد البايتات السابقة الصحيحة من تخمين المهاجم.
ثم طرح الباحث السؤال:
"هل يمكن تضخيم هذا الفرق الزمني الصغير بما يكفي، عبر الشبكة، لاسترداد تجزئة HMAC كاملة مكونة من 44 حرفًا مشفرًا بـ Base64؟"
اتضح أن الإجابة هي نعم — مع تكرار قياسات كافية وبعض التحليل الإحصائي، يرتفع الإشارة فوق الضوضاء.
عندما يريد عميل Trilium المزامنة، يستدعي POST /api/login/sync مع جسم JSON كالتالي:
{
"timestamp": "2025-12-19T10:00:00.000Z",
"syncVersion": 34,
"hash": "<HMAC-SHA256 لـ documentSecret + timestamp، مشفر بـ Base64>"
}
يعمل الاسترداد بايتًا بايت كما يلي:
للموضع = 0 إلى 43:
لكل حرف مرشح ج في مجموعة الأحرف (A-Z, a-z, 0-9, +, /, =):
أرسل SAMPLES طلبًا مع hash = known_prefix + ج + padding
سجل متوسط وقت الاستجابة
أفضل حرف = المرشح الذي لديه أعلى متوسط وقت
أضف أفضل حرف إلى known_prefix
بعد 44 تكرارًا (واحد لكل حرف Base64)، يتم استرداد تجزئة HMAC كاملة المكونة من 44 حرفًا.
المتطلبات العملية:
time.perf_counter() في بايثون يوفر دقة نانوثانية)انظر poc.py للحصول على إثبات مفهوم موثق بالكامل بلغة بايثون.
ملخص سريع لما يفعله إثبات المفهوم:
A–Z, a–z, 0–9, +, /, =)./api/login/sync لكل مرشح ويقيس متوسط وقت الاستجابة الوسيط.إخلاء مسؤولية: تم توفير إثبات المفهوم هذا للأغراض التعليمية وبحوث الأمان المسؤولة فقط. لا تستخدمه ضد أنظمة لا تملكها أو لديك إذن كتابي صريح لاختبارها.
تقوم عوامل المقارنة !== (و ===) في 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
الاستغلال الناجح يمنح المهاجم:
هذا شديد الخطورة بشكل خاص للمستخدمين الذين يخزنون بيانات شخصية حساسة (كلمات مرور، مستندات خاصة، إدخالات يوميات) في قاعدة معرفة 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
docker pull zadam/trilium:0.101.0
لا تستخدم أبدًا === / !== لمقارنة الأسرار. عوامل المساواة في JavaScript ليست ثابتة زمنيًا. أي مقارنة لتجزئات HMAC أو الرموز المميزة أو كلمات المرور باستخدام === / !== هي وحي توقيت محتمل.
استخدم دائمًا crypto.timingSafeEqual() في Node.js (أو ما يعادله في لغتك/بيئة التشغيل) عند مقارنة القيم التشفيرية. هذه هي واجهة البرمجة القياسية المصممة خصيصًا لهذه المهمة.
هجمات التوقيت حقيقية عبر الشبكة. على الرغم من أن الاختلافات في النانوثانية قد تبدو مستحيلة الكشف عبر الإنترنت، إلا أن التقنيات الإحصائية والعينات الكافية يمكنها استخراج إشارة واضحة من القياسات المزعجة — خاصة في البيئات منخفضة التقلبات.
تحديد المعدل وحده ليس تخفيفًا كافيًا. حتى مع تحديد المعدل لكل IP، يمكن للمهاجم الذي لديه إمكانية الوصول إلى بروكسيات دوارة أو شبكة بوت تجميع عينات كافية لاستغلال الفرق الزمني.
التحقق من HMAC يستحق نفس العناية التي تستحقها مقارنة كلمات المرور. تجزئات HMAC هي أسرار. تعامل مع أي مقارنة لقيمة سرية كما لو كان من الممكن استغلال القنوات الجانبية للتوقيت.
مراجعة الكود لأنماط التشفير أمر ضروري. تم اكتشاف هذه الثغرة من خلال المراجعة اليدوية — سطر واحد من الكود بدا غير ضار ولكن له تداعيات أمنية خطيرة. تساعد عمليات تدقيق الأمان المخصصة للتشفير/الأمان في اكتشاف هذه المشكلات مبكرًا.
| التاريخ | الحدث |
|---|---|
| 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 | GHSA-hxf6-58cx-qq3x |
| طلب السحب للإصلاح | TriliumNext/Trilium#8129 |
| سجل CVE (CVEProject) | CVE-2025-68621.json |
| CWE-208 | تباين توقيت ملحوظ |
| مستودع Trilium Notes | TriliumNext/Trilium |
تمت صيانة هذا المستودع للأغراض التعليمية والبحثية وفقًا لمبادئ الكشف المسؤول.