
استمرار الجلسة في listmonk بعد إعادة تعيين كلمة المرور وتغييرها
استمرارية جلسات listmonk بعد إعادة تعيين كلمة المرور وتغييرها
اكتشفت هذه المشكلة أثناء مراجعة listmonk، وهو مدير نشرات إخبارية وقوائم بريدية مفتوح المصدر، وكان في ذهني سؤال أمني بسيط:
عندما يغيّر المستخدم كلمة المرور أو يعيد تعيينها، هل يقوم التطبيق فعليًا بإنهاء الجلسات الصادرة مسبقًا؟
في هذه الحالة، كانت الإجابة لا.
ظلت الجلسات المصادق عليها الصادرة مسبقًا صالحة بعد كل من:
هذا يعني أن كوكي جلسة مسروقة يمكن أن تبقى قادرة على الصمود في وجه الأحداث الأمنية نفسها التي يعتمد عليها المستخدمون لاسترداد حساباتهم.
تم قبول المشكلة وتخصيص CVE-2026-34828 لها.
المشروع: listmonk على GitHub
CVE: CVE-2026-34828
أثرت هذه المشكلة على listmonk، وهو مشروع واسع الانتشار مع 5M+ من عمليات سحب Docker.
جلسة مصادق عليها مسروقة → الضحية يعيد تعيين كلمة المرور أو يغيّرها → تبقى الجلسة القديمة صالحة → يحتفظ المهاجم بالوصول إلى الحساب بعد استرداد بيانات الاعتماد
listmonk هو مدير قوائم بريدية ونشرات إخبارية مستضاف ذاتيًا.
يوفر:
هذا يعني أن نموذج الجلسات الخاص به يشكّل حدًا أمنيًا حقيقيًا.
السؤال المهم هنا لم يكن ما إذا كان listmonk يدعم إعادة تعيين كلمة المرور.
السؤال الحقيقي كان:
هل تؤدي إعادة تعيين كلمة المرور أو تغييرها إلى إلغاء استمرارية وصول المهاجم فعليًا إذا كانت الجلسة قد سُرقت بالفعل؟
في هذه الحالة، لم يحدث ذلك.
الكثير من المراجعات الأمنية تركّز بشكل ضيق جدًا على تجاوزات تسجيل الدخول وتصعيد الامتيازات الواضح.
وهذا يغفل فئة مهمة من نقاط الضعف:
فشل الاسترداد
إذا غيّر المستخدم كلمة المرور أو أعاد تعيينها، فمن المفترض أن يكون لهذا الإجراء معنى. من المفترض أن يقلل الثقة في بيانات الاعتماد الأقدم وحالة المصادقة القديمة.
إذا كان المهاجم يمتلك بالفعل جلسة صالحة ونجت تلك الجلسة من حدث الاسترداد، فإن الضحية لم يسترد الحساب بالكامل فعليًا.
كانت هذه هي المشكلة هنا.
لم تكن هذه ثغرة في التحقق من تسجيل الدخول. لم تكن مشكلة تشفير. لم تكن فشلًا في تجزئة كلمات المرور.
بل كانت فشلًا في دورة حياة الجلسة:
وهذا كافٍ لإنشاء ثغرة حقيقية.
لم أتعامل مع listmonk عبر مهاجمة النقاط الطرفية بشكل عشوائي وأمل أن تسقط إحداها.
المسار الأقوى كان تحديد حد الثقة الأعلى قيمة أولًا.
بالنسبة للبرمجيات المعتمدة بشدة على المصادقة، فإن أحد أفضل الحدود لاختبارها هو:
هل تلغي تغييرات الحساب الحساسة أمنيًا الجلسات الموثوقة سابقًا؟
عادةً ما يصبح هذا السؤال مثيرًا للاهتمام حول:
في listmonk، جاءت أقوى الإشارات من الأولين.
هناك أصبحت المشكلة واضحة.
لم تكن الثغرة أن تغييرات كلمة المرور تفشل.
كانت الثغرة أن الجلسات عاشت لفترة أطول منها.
من مراجعة المصدر، مسار إعادة تعيين كلمة المرور:
لكن لم يكن هناك إلغاء مرئي للجلسات الأقدم.
ظهر النمط نفسه في مسار تغيير كلمة المرور المصادق عليه:
طابق هذا السلوك النتائج الفعلية تمامًا.
مناطق الكود ذات الصلة التي راجعتها كانت:
cmd/auth.go لسلوك النسيان/إعادة التعيينcmd/users.go لتحديثات الملف الشخصي المصادق عليهاinternal/core/users.go لمعالجة تحديث كلمة المرورلأن سرقة الجلسة شرط هجوم حقيقي.
بمجرد أن يحصل المهاجم على كوكي جلسة مصادق عليها صالحة بأي وسيلة مثل:
يجب أن يكون بمقدور الضحية إنهاء استمرارية وصول المهاجم عن طريق تغيير كلمة المرور أو إعادة تعيينها.
هنا، لم يستطيعوا ذلك.
كانت سلسلة الهجوم مباشرة:
تلك هي الثغرة بأكملها.
التمييز المهم هو الاستمرارية بعد الاسترداد.
الكثير من التطبيقات تتعامل مع تغيير كلمة المرور كحدث على مستوى بيانات الاعتماد فقط. وهذا ليس كافيًا.
السؤال الحقيقي ليس:
«هل تغيّرت قيمة كلمة المرور في التخزين؟»
السؤال الحقيقي هو:
«هل تم إلغاء علاقة الثقة المرتبطة بالجلسات الأقدم؟»
في listmonk، لم يحدث ذلك.
هذا يحوّل ما كان يمكن أن يكون صيانة حساب عادية إلى استرداد أمني غير مكتمل.
هذا هو الفرق بين:
تحققت من المشكلة في مسارين منفصلين.
أولًا، أنشأت مستخدم اختبار عاديًا وسجّلت الدخول، محتفظًا بكوكي الجلسة المصادق عليها.
ثم قمت بتشغيل مسار «نسيت كلمة المرور»، والتقطت رابط إعادة التعيين، وأعدت تعيين كلمة المرور.
بعد إعادة التعيين:
كان طلب تحقق تمثيلي بالشكل التالي:
GET /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<old_pre_reset_session>
وما زال الخادم يعيد:
HTTP/1.1 200 OK
Content-Type: application/json
مع الملف الشخصي المصادق عليه.
أثبت ذلك الادعاء الأساسي:
ثم تحققت من الفئة نفسها من الثغرات في مسار تغيير كلمة المرور المصادق عليه.
سجّلت الدخول مرتين بنفس المستخدم وحفظت جلستين مصادق عليهما صالحتين:
باستخدام الجلسة A، غيّرت كلمة المرور من خلال نقطة تحديث الملف الشخصي.
مثال على الطلب:
PUT /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<session_A>
Content-Type: application/json
{
"name":"victim1",
"email":"[email protected]",
"password":"VictimChanged123"
}
بعد ذلك:
طلب متابعة باستخدام الجلسة B ما زال يعيد بيانات مصادق عليها من /api/profile.
أثبت ذلك أن المشكلة لم تقتصر على مسار النسيان/إعادة التعيين. بل أثرت أيضًا على تغييرات كلمة المرور العادية المصادق عليها.
كانت إعادة إنتاج واحدة كافية لإظهار وجود مشكلة.
لكن التحقق من المسارين معًا كان مهمًا لسببين.
أظهر أن الثغرة لم تكن معزولة في مسار استرداد واحد لحالة حافة.
نفس الخاصية الأمنية فشلت في:
جعل من الصعب تجاهل المشكلة باعتبارها منطق أعمال عرضيًا.
كانت هذه بوضوح نقطة ضعف أوسع في إدارة الجلسات:
أعطى ذلك المشكلة وزنًا أمنيًا أقوى بكثير.
اختبرت أيضًا مسار إعادة التعيين على حساب مفعّل بـ TOTP لأنني أردت معرفة ما إذا كانت إعادة تعيين كلمة المرور ستُضعف أو تتجاوز توقعات المصادقة الثنائية بصمت.
ما أكدته هو:
كان ذلك فحصًا مفيدًا للحدود.
ضيّق المشكلة بشكل صحيح.
لم تكن الثغرة:
المشكلة الحقيقية بقيت:
هذه نتيجة أنظف وأكثر قابلية للدفاع عنها.
صُنّفت هذه المشكلة بشكل معقول على أنها عالية الخطورة.
التأثير الرئيسي هنا هو الوصول غير المصرح به المستمر بعد إجراءات استرداد أمان الحساب.
كان تصنيف النشرة:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:Nهذا منطقي.
الادعاء ليس أن المهاجم يمكنه تسجيل الدخول دون بيانات اعتماد من العدم. الادعاء هو أنه بمجرد حصول المهاجم على جلسة مصادق عليها صالحة، لا يمكن للضحية إنهاء هذا الوصول بالكامل من خلال تنفيذ الإجراءات الأمنية نفسها التي يفترض أن تسترد الحساب، وهي إعادة تعيين كلمة المرور وتغييرها.
تلك ثغرة حقيقية وقابلة للدفاع عنها في إدارة الجلسات.
بعض الناس يقللون من شأن ثغرات استمرارية الجلسة لأنهم يفترضون أن سرقة الجلسة تعني بالفعل «انتهت اللعبة».
هذا تبسيط مفرط.
السؤال الحقيقي هو ما الذي يحدث بعد أن يلاحظ الضحية أن هناك خطأ ما ويتخذ إجراءً.
إذا:
عندها يكون استرداد الحساب غير مكتمل.
هذا ليس مجرد سلوك محرج. بل هو فشل أمني في نموذج الاسترداد.
خاصة في منصة موجهة للمسؤولين، فهذه مشكلة ذات تأثير كبير على السرية.
أصلح المُطوّر المشكلة في الالتزام:
db82035
اتجاه الإصلاح الأساسي هو بالضبط ما كانت تحتاجه هذه الثغرة:
هذا هو العلاج الصحيح لأنه يستهدف الخاصية الأمنية الحقيقية التي فشلت:
يجب أن تموت الثقة الأقدم عندما تتغير بيانات الاعتماد
الإصلاح الجيد لهذه الفئة من الثغرات لا يتعلق بتغيير التحقق من كلمة المرور. بل يتعلق بإلغاء حالة الجلسة النشطة سابقًا المرتبطة بالحساب. ذلك هو الجزء الذي يعيد الاسترداد الفعلي.
تم الإبلاغ عن هذه المشكلة بشكل خاص عبر مسار الإبلاغ الأمني في GitHub.
قام المُطوّر:
CVE-2026-34828
أحد الأمور التي ظهرت أثناء معالجة النشرة كان النطاق.
تضمن التقرير الأصلي كلا من:
تعامل GitHub في البداية مع هاتين المشكلتين على أنهما قابلتان للإصلاح بشكل مستقل لأغراض تخصيص CVE. هذا تذكير مفيد بأن نطاق النشرة مهم حتى عندما تكون نقطة الضعف الأساسية متشابهة من الناحية المفاهيمية.
كانت النتيجة النهائية CVE-2026-34828.
الدرس الرئيسي هنا بسيط:
تغيير بيانات الاعتماد ليس كافيًا إذا كانت الثقة المصادق عليها القديمة ما تزال حية.
الكثير من المطورين يفكرون من منظور:
تلك الأمور مهمة.
لكن الحد الأمني الحقيقي أوسع:
عندما يحدث حدث حساب عالي الخطورة، ما هي الحالة الموثوقة سابقًا التي يجب أن تتوقف عن كونها موثوقة؟
في هذه الحالة، كان ينبغي أن يكون الجواب:
وكان listmonk لا يفعل ذلك.
هذه هي الخلاصة الحقيقية.
لم تكن هذه الثغرة حول حمولات برمجية متقنة أو حيل ذكية في المحللات.
بل كانت حول طرح السؤال الصحيح حول حدود الثقة.
في listmonk، تغيّرت كلمة المرور. اكتمل إجراء الاسترداد. لكن جلسة المهاجم القديمة ما زالت حية.
لهذا أصبحت هذه CVE-2026-34828.
