
ابحث عن آثار استغلال CVE-2026-18963 (الاستيلاء على الحساب دون مصادقة في Keycloak) في قاعدة بيانات Keycloak
سكربت psql يبحث في قاعدة بيانات PostgreSQL الخاصة بـ Keycloak عن آثار استغلال
CVE-2026-18963 (استيلاء على الحساب دون مصادقة عبر تدفق إعادة تعيين بيانات الاعتماد).
نُشر بواسطة KYOS. كتبناه أثناء ترقيع عمليات نشر Keycloak التي نديرها، للتحقق من عدم استيلاء أي أحد على أي حساب خلال فترة التعرّض.
CVE-2026-18963 هو خلل في تدفق إعادة تعيين بيانات الاعتماد في keycloak-services
(keycloak#51833). يمكن لمهاجم
غير مُصادَق إكمال عملية إعادة تعيين كلمة المرور لأي مستخدم دون النقر على رابط
التحقق عبر البريد، ثم تعيين كلمة مرور جديدة على الحساب. لا يُشترط أي تفاعل من المستخدم.
تم التحقق منها مقابل Keycloak 26.7.1 على نسخة مخصصة للاختبار. نحن لا ننشر خطوات إعادة الإنتاج؛ التفاصيل أدناه هي ما تحتاجه لقراءة الاستعلامات.
يتتبع التدفق شاشة المصادقة التي يوجد عليها بطريقة غير مرتبطة بالخطوة التي عيّنتها.
يتيح ذلك إعادة الدخول إلى تدفق إعادة تعيين بيانات الاعتماد عند خطوة التحقق عبر
البريد، حيث تُرجع الدالة المصابة ResetCredentialEmail.action() نجاحًا دون التحقق
مطلقًا من وجود رمز إجراء (action token). ثم تستمر عملية التنفيذ إلى نموذج تحديث
كلمة المرور.
هناك نتيجتان تشكّلان كل استعلام في هذا المستودع:
SEND_RESET_PASSWORD عاديًا.
غياب هذا الحدث ليس بصمة مميزة.action() تضع علامة "مُتحقَّق منه" على عنوان البريد الإلكتروني
للحساب قبل أن تُرجع. المسار الشرعي لا يصل إلى action() إطلاقًا، لذا فهذا
تغيير في الحالة لا ينتجه سوى تجاوز الحماية. الاستعلام Q3 يعتمد عليه.الإصلاح (keycloak#51844)
يربط حالة الشاشة بعملية التنفيذ التي عيّنتها، ولا يسمح لـ action() بالنجاح إلا
عند وجود رمز إجراء لذلك المستخدم تحديدًا.
الخطورة: حرجة، CVSS v3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N)،
وفقًا لـ Red Hat و
NVD. السبب الجذري هو التحقق
غير السليم من الحالة في تدفق المصادقة، وتم إصلاحه في
keycloak#51844.
| السلسلة | مُصلحة في |
|---|---|
| 26.7.x | 26.7.2 (ملاحظات الإصدار) |
| 26.6.x | 26.6.6 |
| 26.4.x (LTS) | 26.4.15 |
| 26.8 | 26.8.0 |
كل ما هو أدنى من هذه الإصدارات في السلاسل المدعومة مُعرّض للثغرة. الإصدارات الخارجة عن الدعم (26.5.x و26.3 وما أقدم) لا تحصل على إصلاح: افترض التعرّض ورقِّ إلى سلسلة مُصلحة. تُدرج Red Hat إصدار RH-SSO 7 القديم كغير متأثر؛ أما Red Hat Build of Keycloak 26.4/26.6 فمُصلح في 26.4.15-1 / 26.6.6-1.
تعطيل إعادة تعيين كلمة المرور الذاتية يزيل نقطة الدخول المصابة:
وحدة تحكم المسؤول > إعدادات Realm > تسجيل الدخول > إيقاف "Forgot password"،
لكل Realm. عبر واجهة برمجة تطبيقات المسؤول: PUT /admin/realms/{realm} مع
{"resetPasswordAllowed": false}.
هذا يحجب نقطة النهاية login-actions/reset-credentials لكنه يمنع أيضًا المستخدمين
الشرعيين من إعادة تعيين كلمات مرورهم، لذا تعامل معه كحل مؤقت حتى ترقّي. لاحظ أن
Red Hat لا تُدرج تخفيفًا معتمدًا لهذه الثغرة؛ الترقيع هو الإصلاح الحقيقي الوحيد.
الإصدار الأول من هذا السكربت كان يُعلّم على عملية إعادة تعيين مكتملة بلا
حدث SEND_RESET_PASSWORD قبلها بفترة قصيرة.
كان ذلك خطأً، وقد
أشار إليه vilmosnagy
بعد إعادة إنتاج ثغرة CVE على نسخته الخاصة ومشاهدة الحدث يظهر
رغم ذلك. والسبب هو النتيجة الأولى أعلاه: يعمل مصادق البريد
بشكل طبيعي، فيتلقى الضحية رسالة إعادة تعيين حقيقية ويسجّل Keycloak حدث
SEND_RESET_PASSWORD عاديًا. كان الفحص معكوسًا. كان سيمسح كل
حساب مخترق يفحصه. وقد أُزيل.
تعاني مخططات التوقيت من مشكلة أصغر لكنها حقيقية. يعتمد النهج في
سكربت البحث الخاص بـ thomasdelorge
على ربط SEND_RESET_PASSWORD مع UPDATE_PASSWORD عبر code_id مشترك
ويرصد فجوة قصيرة. مفتاح الربط صحيح، والمؤلف صريح
في أن العتبة استدلالية. واختبارنا يقول الشيء نفسه: إعادة تعيين تم تجاوزها
وأخرى مشروعة أُكملت في المتصفح نفسه تنتج أنواع أحداث متطابقة، ومفاتيح تفاصيل متطابقة، ونفس code_id. الفرق الوحيد هو
التأخير، مما يجعل العتبة تحمل الحكم كله. من ليس في عجلة يبقى
تحتها. أما المستخدمون الحقيقيون فيتجاوزونها، لأن code_id يبقى نفسه فقط عندما يُفتح الرابط في المتصفح الذي بدأ
التدفق، وهي أيضًا الحالة التي تكون فيها الرسالة على الأرجح مفتوحة بالفعل.
لم نجد أي حدث أو تفصيل حدث أو خطأ يفصل بين الحالتين. لا يوجد حدث "تم تخطي خطوة البريد" للبحث عنه. ما يوجد فعلًا هو أثر جانبي على الحالة، وهذا ما تستخدمه Q3.
يشغّل cve-2026-18963-keycloak-hunt.sql ست استعلامات للقراءة فقط:
| الاستعلام | ما يجده |
|---|---|
| Q0 | ما إذا كان النطاق يخزّن الأحداث أصلًا، ومدة صلاحيتها (TTL)، وما إذا كانت تفاصيل الأحداث تُحفظ. يعتمد Q2 على تفصيل code_id، لذا إذا كانت التفاصيل مفقودة يكون Q2 أعمى وصمته لا يعني شيئًا. |
| Q1 | كل تعيين لبيانات اعتماد كلمة المرور داخل نافذة التعرض (credential.created_date). هذا هو الاستيلاء نفسه، ويبقى بعد انتهاء صلاحية الأحداث. |
| Q2 | كل تحديث مكتمل لبيانات الاعتماد، مربوطًا برسالة إعادة التعيين لجلسة المصادقة نفسها، ومصنَّفًا. انظر أدناه. |
| Q3 | الأثر الجانبي الحتمي: المستخدمون الذين لديهم email_verified دون سبب مشروع. |
| Q4 | عمليات إعادة تعيين بيانات الاعتماد عبر Admin-API وكتابات المستخدمين، لتفسير صفوف Q1 وQ3 واستبعاد المسار الذي يقوده المسؤول. |
| Q5 | الجدول الزمني الخام لأحداث إعادة التعيين/بيانات الاعتماد، لقراءة حالة واحدة يدويًا. |
| الحكم | المعنى |
|---|---|
NO_MAIL_WAS_SENT | يتشارك تحديث بيانات الاعتماد code_id مع إرسال فاشل: لا يوجد عنوان على الحساب، أو رفضه SMTP. لم يغادر أي رابط الخادم قط، لذا لم يكن بإمكان أحد فتحه، ومع ذلك وصل التدفق إلى نموذج كلمة المرور. مستحيل بشكل مشروع؛ تعامل معه كاستغلال. |
CLEARED_TOKEN_CLICKED | اكتملت إعادة التعيين تحت code_id مختلف عن أي SEND_RESET_PASSWORD لذلك المستخدم. لا ينتج ذلك إلا عن رمز إجراء (action token) استُهلك في متصفح أو جلسة أخرى، لذا فقد نُقر على رابط البريد فعلًا. مشروع بشكل حتمي. |
CANDIDATE | نفس code_id الخاص بالإرسال. هكذا يبدو التجاوز، وهكذا تبدو أيضًا إعادة تعيين مشروعة في المتصفح نفسه. ليس اكتشافًا بحد ذاته. |
NOT_RESET_FLOW | لا يوجد SEND_RESET_PASSWORD لذلك المستخدم في النافذة؛ تغيّرت كلمة المرور بطريقة أخرى (وحدة تحكم الحساب، أو Admin API، أو التسجيل). تحقق من Q4. |
تُطبع secs_after_send للسياق. لا تستخدمها كحكم.
تضع ResetCredentialEmail.action() المصابة علامة التحقق على عنوان البريد
الإلكتروني للحساب قبل أن تنجح. أما المسار المشروع فلا يدخل
action() إطلاقًا، لأن authenticate() يتجاوزها عند وجود رمز الإجراء،
لذا فإن إعادة تعيين حقيقية تترك email_verified دون تغيير. تحققنا من ذلك على 26.7.1 لإعادة
تعيينات مشروعة أُكملت في المتصفح الذي بدأها وفي
متصفح آخر. كلتاهما أبقت مستخدمًا غير موثّق غير موثّق. وكلتا إعادتي التعيين المتجاوزتين
قلبت العلامة.
فالحساب الذي تغيّرت كلمة مروره داخل النافذة الزمنية، والذي أصبح الآن
email_verified، دون وجود حدث VERIFY_EMAIL/UPDATE_EMAIL ودون أي كتابة
من المسؤول عليه، ليس لديه تفسير بريء لكونه موثّقًا.
ثمة قيد واحد بالغ الأهمية هنا: الحسابات التي كانت موثّقة بالفعل
قبل النافذة الزمنية غير مرئية لـ Q3، لأن العلامة كانت بالفعل true.
فـ Q3 دقيق وليس شاملًا. أقوى صفوفه هو verified_with_no_email = t،
لأن حسابًا بلا عنوان على الإطلاق لا يمكنه استقبال رابط إعادة تعيين ولا
أن يُوثَّق بفتح أحدها.
# defaults: realm 'master', window since 2026-06-01
psql -U keycloak -d keycloak -f cve-2026-18963-keycloak-hunt.sql
# explicit realm and window (run once per realm; plain values, psql quotes them)
psql -U keycloak -d keycloak \
-v realm=myrealm -v since=2026-05-01 \
-f cve-2026-18963-keycloak-hunt.sql
عيّن since إلى ما قبل نشر النسخة المصابة مباشرةً. على Kubernetes: