
مختبر Docker قابل لإعادة الإنتاج + إثبات مفهوم (PoC): تصعيد الصلاحيات من Keycloak manage-users إلى realm-admin عبر الأدوار المركّبة (نسخة CVE-2026-94000). تم التحقق منه على 26.8.1.
مختبر Docker قابل لإعادة الإنتاج وإثبات مفهوم (PoC) لمسار تصعيد امتيازات في واجهة REST الإدارية في Keycloak: مدير مفوَّض يملك فقط manage-users يمكنه تصعيد نفسه (أو أي مستخدم أو مجموعة) إلى realm-admin الكامل عبر إسناد دور مركّب يكون اسمه عاديًا لكن أدواره الفرعية تتضمن realm-management/realm-admin.
تم التأكيد من البداية إلى النهاية مقابل الصورة الرسمية quay.io/keycloak/keycloak:26.8.1.
⚠️ بحث مصرَّح به فقط. كل شيء يعمل مقابل Keycloak مؤقت على
localhost. لا توجّه إثبات المفهوم نحو أنظمة غير مصرَّح لك صراحةً باختبارها.
شدّد Keycloak 26.8.1 إجراء الانضمام إلى المجموعات: لم يعد بإمكان مدير يملك manage-users
إضافة مستخدم إلى مجموعة تمنح بشكل متعدٍّ أدوار إدارة realm-management.
تسير GroupUtils.checkAdminGroupRoles عبر أدوار المجموعة بشكل تعاودي وتستدعي
requireMapRole على كل دور إداري متداخل:
private static void requireMapRoleRecursive(RoleModel role, AdminPermissionEvaluator auth, Set<String> visited) {
if (!visited.add(role.getId())) return;
if (AdminRoles.isAdminRole(role)) auth.roles().requireMapRole(role); // checks nested admin roles
if (role.isComposite()) role.getCompositesStream().forEach(child -> requireMapRoleRecursive(child, auth, visited));
}
لم تتم إضافة نفس المسح التكراري إلى نقاط نهاية تعيين الأدوار. عندما يُعيَّن دور مباشرةً على مستخدم أو مجموعة، يتحقق الخادم فقط من الدور المُرسَل:
// RoleMapperResource.addRealmRoleMappings — mounted on BOTH users and groups
for (RoleRepresentation role : roles) {
RoleModel roleModel = realm.getRole(role.getName());
auth.roles().requireMapRole(roleModel); // posted role only — children never inspected
roleMapper.grantRole(roleModel);
}
وبوابة الصلاحيات نفسها تعتمد على اسم الدور، وليس على محتوياته. في
RolePermissionsV2.canMapRole، لا يُعتبر الدور ذا امتيازات إلا عندما تكون حاويته
realm-management/master و يكون اسمه ضمن AdminRoles.ALL_ROLES:
public boolean canMapRole(RoleModel role) {
if (isRealmAdminRole(role)) { ... } // isRealmAdminRole = container + ALL_ROLES.contains(name)
if (root.hasOneAdminRole(AdminRoles.MANAGE_USERS)) // manage-users may map ANY non-admin-named role
return true;
...
}
لذا فإن مركّبًا (composite) يُدعى sneaky-composite يغلّف realm-management/realm-admin يمرّ
عبر البوابة — فاسمه ليس في ALL_ROLES، وأبناؤه لا يُمرّ عليها أبدًا. تمتد الفجوة
عبر ثلاث نقاط نهاية:
RoleMapperResource.addRealmRoleMappings — المستخدمون والمجموعاتClientRoleMappingsResource.addClientRoleMapping — أدوار العميل على أي عميلالأثر الصافي: الحمولة المميّزة (مركّب يغلّف realm-admin) تحتاج فقط إلى
الوجود — أُنشئت بواسطة مسؤول حقيقي، أو استيراد realm، أو عملية سابقة. يمكن لـ manage-users
حينها تعيينها، وهو ما لم يتوخّه نموذج تهديد CVE-2026-94000 قط.
| # | المتغير | لماذا ينجح |
|---|---|---|
| A | التعيين الذاتي لـ sneaky-composite → realm-admin | بوابة الاسم تُغفل المركّب |
| B | تعيين outer-sneaky المتداخل (→ sneaky-composite → realm-admin) على مستخدم | العمق لا يُفحص |
| C | تعيين دور عميل على عميل غير إداري (spa/hidden-ra → realm-admin) | يستخدم ClientRoleMappingsResource البوابة نفسها |
| D | تعيين المركّب على مجموعة لديها أعضاء بالفعل | واجهة برمجية مختلفة عن الانضمام؛ الأعضاء الحاليون يتصاعدون دون أي انضمام |
المتطلبات: Docker + Docker Compose v2، وcurl + jq على المضيف.
cp .env.example .env # optional: pin KC_VERSION (default 26.8.1)
make up # start Keycloak + auto-bootstrap the vulnerable realm `app`
make logs # wait for "Lab realm 'app' is ready"
make check # non-destructive detection (maps, then immediately unmaps)
make exploit # Attack A: self-escalate limited-admin -> realm-admin
make all # full chain: check + A + B + C + D
make reset # undo every escalation (keep the lab running)
make clean # tear everything down
حاوية bootstrap ذات التشغيل الواحد تبني السيناريو عبر واجهة REST الإدارية، ثم تخرج:
| الكائن | الغرض |
|---|---|
limited-admin / limitedpass | المهاجم — يمتلك فقط manage-users |
alice / bob / carol / dave | مستخدمون ضحايا |
sneaky-composite (دور realm) | مركّب → realm-management/realm-admin |
outer-sneaky (دور realm) | مركّب → sneaky-composite (قفزة إضافية واحدة) |
عميل spa + دور spa/hidden-ra | دور عميل غير إداري → realm-admin |
عميل confidential (confsecret) | هدف تأثير قراءة الأسرار |
مجموعة helpdesk (alice عضو فيها) | هدف هجوم تعيين المجموعات |
يحل الـ PoC معرّفات الأدوار بالطريقة التي يستطيع بها مستدعٍ يمتلك manage-users فعليًا — عبر
نقاط النهاية .../role-mappings/.../available — لأن ذلك الحساب يُمنع من
GET /roles/{name} وGET /clients (403) لكن ليس من القوائم المتاحة. هذا يُبقي
السكربت إعادة إنتاج أمينة من منظور مهاجم منخفض الصلاحيات.
وجّه المختبر إلى بناء مُرقّع؛ يجب أن يُرجع الخادم المُصلح 403 عند كل خطوة تعيين مع الاستمرار في السماح بتعيين الأدوار العادية غير الإدارية:
KC_VERSION=<patched-tag> docker compose up -d --force-recreate
make check # expect: NOT vulnerable
مقابل quay.io/keycloak/keycloak:26.8.1 (النص الكامل في docs/demo.txt،
التفاصيل في VALIDATION.md):
| الخطوة | الإجراء | النتيجة |
|---|---|---|
| خط الأساس | limited-admin POST /roles | 403 (صلاحيات منخفضة فعليًا) |
| فحص | ربط sneaky-composite بالذات (ثم فك الربط) | 204 → عرضة للثغرة |
| A | ربط sneaky-composite بالذات → realm-admin → إنشاء دور / استخراج رسائل بريد المستخدمين | 204 / 201 / 200 |
| B | ربط outer-sneaky المتداخل بـ bob → bob ينشئ دورًا | 204 / 201 |
| C | ربط spa/hidden-ra بـ carol → carol تنشئ دورًا | 204 / 201 |
| D | ربط الغلاف بـ helpdesk → العضو الحالي alice ينشئ دورًا | 204 / 201 |
| ضابط D | manage-users يُضمّ عضوًا جديدًا إلى helpdesk | 403 (إصلاح CVE-2026-94000 سليم) |
يُثبت ضابط D عدم التماثل: ضمّ المجموعة محظور، لكن ربط المركّب نفسه بالمجموعة (أو بمستخدم/عميل) لا يُفحص ويُعيد 204.
RolePermissionsV2.canMapRole / RolePermissions.checkAdminRoles، ارفض الدور
عندما يكون AdminRoles.isAdminRoleOrComposite(role) صحيحًا ما لم يكن للمستدعي صلاحية ربط
كل دور إداري متداخل — على غرار GroupUtils.requireMapRoleRecursive.RoleMapperResource.addRealmRoleMappings (المستخدمون والمجموعات) و
ClientRoleMappingsResource.addClientRoleMapping.manage-users لا يمكنه ربط sneaky-composite، أو غلاف متداخل،
أو دور عميل غير إداري يركّب realm-admin، أو ربط الغلاف بمجموعة لها
أعضاء. اختبار ضمّ المجموعة الحالي وحده غير كافٍ.التقرير التقني الكامل: report/report.md.
docker-compose.yml Keycloak + one-shot bootstrap container
bootstrap/setup-realm.sh Builds the vulnerable realm via the admin REST API
poc/kc_composite_maprole_poc.sh Detection + exploitation + cleanup PoC (curl/jq)
report/report.md Full technical write-up
VALIDATION.md / docs/demo.txt Verified run evidence
MIT — انظر LICENSE.