
إثبات مفهوم (PoC) لتصعيد الامتيازات بدون مصادقة لـ WordPress Events Manager < 7.4.1؛ يكتشف تعارض معرفات المنشورات/المستخدمين ويصعّد الأهداف إلى مسؤول عبر REST أو wp-admin.
| المنتج | Events Manager (إضافة ووردبريس) |
| الإصدارات المتأثرة | 7.1.0 – 7.4.0.x |
| الإصدار المُصحَّح | 7.4.1 |
| مواطن الضعف | CWE-269: إدارة امتيازات غير سليمة |
| درجة الخطورة | CVSS 3.1: 9.8 (حرِج) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| معرّف WPVDB | 82767ce2-01e4-46ad-a52b-72d3ab2049bd |
| الباحث الأصلي | Jakub Herman |
| التحليل والـ PoC | ghostpel |
poc.py).تحتوي إصدارات Events Manager من 7.1.0 حتى 7.4.0.x على ثغرة تصعيد امتيازات تسمح لمهاجم غير مُصادَق تمامًا بالاستيلاء على أي حساب مستخدم يتطابق معرّفه مع معرّف أحد منشورات الإضافة نفسها (event / location / event-recurring / location-recurring). يمكن فرض التطابق على المواقع التي تكون فيها حجوزات الضيوف مفعّلة (وهو الوضع الافتراضي): فكل حجز ضيف ينشئ حساب مستخدم حقيقيًا ويجعل عدّاد الزيادة التلقائية لجدول wp_users يتقدم بمقدار واحد، ما يسمح للمهاجم «بالتنقل» في فضاء معرّفات المستخدمين حتى يبلغ معرّف منشور event/location.
السبب الجذري: مُرشِّح map_meta_cap في الإضافة يتجاهل قائمة الصلاحيات التي كان ووردبريس قد حسبها بالفعل ($caps = []) لأي قدرة من قدرات meta، كلما صادف أن معرّف كائن الصلاحية يحل إلى منشور event/location — بما في ذلك صلاحيات ووردبريس الأساسية مثل edit_user وdelete_user وpromote_user وremove_user. قائمة الصلاحيات الفارغة تُقرأ على أنها «لا توجد صلاحية مطلوبة» — أي سماح للجميع، بمن فيهم غير المسجّلين دخولًا.
التبعات (كلها بدون مصادقة، عبر واجهة WP REST API): تغيير كلمة مرور أي حساب متطابق المعرّف، أو ترقيته إلى administrator، أو حذفه.
الإصدار الذي خضع للتحليل: 7.4.0.1 (المتأثر) — بمقارنة فروقه مع 7.4.1 (المُصحَّح).
| الإصدار | الحالة |
|---|---|
| ≤ 7.0.5 | غير متأثر (em_map_meta_cap القديم لا يتعامل إلا مع صلاحيات EM الخاصة بها) |
| 7.1.0 – 7.4.0.x | متأثر (نظام archetype مع map_meta_cap المعيب أُدخل في 7.1.0) |
| 7.4.1 | مُصحَّح |
map_meta_cap يُفرِغ $capsclasses/em-archetypes.php (version 7.4.0.1):
| السطر | الكود | الدور |
|---|---|---|
:33 | add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 ); | مُرشِّح عام غير مشروط — يعمل مع كل فحص قدرة meta-capability في ووردبريس، بما في ذلك فحوصات النواة وتلك الخاصة بغير المسجّلين دخولًا |
:547 | if ( !empty( $args[0] ) ) { | يُؤخذ كائن الصلاحية على أنه معرّف منشور (post ID) |
:548 | $post = get_post($args[0]); | تطابق المعرّفات: معرّف المستخدم المستهدف (لصلاحيات مثل edit_user) يُعامَل على أنه معرّف منشور |
:551 | `if( empty($post->post_type) | |
:563 | `if ( !empty( $c['read'][$post->post_type] ) | |
:564–565 | $caps = []; | يتم تجاهل قائمة الصلاحيات بدون أي شرط — حتى وإن كانت الصلاحية المطلوبة ليست قدرة archetype من فئة read/edit/delete_event |
:568/577/584 | فروع if/elseif | لا يُعاد ملء $caps إلا عندما يطابق $cap تمامًا قدرة meta من نوع archetype؛ أما بالنسبة لـ edit_user وdelete_user وpromote_user وremove_user فلا يوجد أي فرع يطابقها → تبقى $caps فارغة |
:597 | return $caps; | المصفوفة الفارغة = «لا توجد صلاحية مطلوبة» = سماح للجميع، بما في ذلك معرّف المستخدم 0 |
النتيجة: كل فحص من فحوصات current_user_can('edit_user', X) أو delete_user أو promote_user أو remove_user يعيد TRUE كلما كان X مساويًا لمعرّف منشور event/location. الإضافة «تتجاهل قرارات التحكم بالوصول التي كان ووردبريس قد اتخذها بالفعل» — تمامًا كما وصف WPScan.
تم التحقق بمقارنة الفروق بين 7.4.0.1 و7.4.1: أصبحت عملية التصفير الآن مشروطة بمطابقة دقيقة للصلاحية في كل فرع (مثل && $c['read'][$post->post_type] == $cap)، لذلك لا تُفرَّغ $caps إلا عندما تكون الصلاحية المطلوبة قدرة meta من نوع archetype فعلًا. تعليق التصحيح: «التصفير عند أي صلاحية تحمل كائنًا كان يفرّغ قائمة المتطلبات لصلاحيات غير ذات صلة (مثل edit_user وpromote_user)، وهو ما يُقرأ على أنه سماح.»
نقاط نهاية REST الخاصة بالمستخدمين (/wp-json/wp/v2/users/{id}) لا تخضع لبوابة تسجيل دخول عامة — فالتحكم بالوصول يتم حصريًا عبر استدعاءات التحقق من الصلاحيات (permission callbacks)، وكلها تمر عبر مُرشِّح map_meta_cap نفسه:
| الإجراء | نقطة النهاية / دالة النواة | الصلاحية المتجاوزة |
|---|---|---|
| تغيير كلمة مرور الحساب المستهدف | PUT /wp-json/wp/v2/users/{id} مع {"password":"..."} → wp_update_user() (فحص edit_user داخلي) | edit_user |
| ترقية الحساب إلى Administrator | PUT /wp-json/wp/v2/users/{id} مع {"roles":["administrator"]} | promote_user |
| حذف حساب | DELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (فحص delete_user داخلي) | delete_user |
| (تعدد المواقع) إزالة مستخدم من المدونة | remove_user | remove_user |
أثر جانبي: edit_comment يتأثر أيضًا عندما يصادف أن يتساوى معرّف تعليق مع معرّف منشور event/location (جدول wp_comments يتشارك الفضاء الرقمي نفسه).
التطابق موجود لأن wp_users.ID وwp_posts.ID متتاليتان مستقلتان للزيادة التلقائية (auto-increment) تتداخلان حتمًا. حجوزات الضيوف تفرضه:
em-install.php:896 — dbem_bookings_anonymous = 1: حجوزات الضيوف مفعّلة افتراضيًا؛ :871 dbem_bookings_registration_disable = 0 (التسجيل مفعّل).em-actions.php:340-344 — booking_add موجود ضمن $booking_nopriv_actions → يمكن استدعاؤه بدون تسجيل دخول (عبر wp_ajax_nopriv_booking_add، بأولوية 999999، الأسطر :817-830، أو عبر wp_loaded :11).em-actions.php:360-375 — مسار booking_add: em_verify_nonce('booking_add') → get_post() → validate() → em_booking_add_registration() → $EM_Bookings->add(). (الـ nonce هو حماية CSRF فقط — nonce الخاص بـ booking_add يُعرض في نموذج الحجز العام، لذا يمكن لأي شخص الحصول عليه.)em-functions.php:405-417 — فرع الضيوف: em_register_new_user($user_data) مع البريد الإلكتروني للمهاجم.em-functions.php:510 — wp_insert_user($user_data) → يجعل عدّاد الزيادة التلقائية لجدول wp_users يتقدم بمقدار 1 عند كل حجز ضيف → يتحكم المهاجم بمعدل نمو عدّاد معرّفات المستخدمين حتى يبلغ معرّف منشور event/location المطلوب (WPScan: «كل حجز ضيف ينشئ حساب مستخدم حقيقيًا ويجعل عدّاد معرّفات المستخدمين يتقدم بمقدار واحد»).سلسلة ثانوية رُصدت أثناء المراجعة (إسناد الحجز عبر person_id) — events-manager.php:430-437 (em_load_event، و$EM_Person من $_REQUEST['person_id'] بدون تسجيل دخول)، em-booking.php:1060-1072 (get_person() يستبدل person_id لحجز جديد)، em-booking.php:2004-2006 (can_manage() = TRUE لحجز بدون معرّف)، em-functions.php:448-449 — لم تتغير في 7.4.1؛ إنه ناقل منفصل (إسناد حجز خاطئ)، وليس جوهر هذا الـ CVE.
/wp-json/wp/v2/event، أو تخمين المعرّفات المتتالية). لنفترض أن الهدف هو N.