
تحليل السبب الجذري، ومدقق إصدار سلبي، وإثبات مفهوم مختبري لـ CVE-2026-18322، وهو تصعيد صلاحيات دون مصادقة في إضافة Smart Popup by Supsystic لـ WordPress.
بحث أمني حول CVE-2026-18322، وهي ثغرة تصعيد صلاحيات غير مصادَق عليها في إضافة
ووردبريس Smart Popup by Supsystic (popup-by-supsystic). يحتوي هذا المستودع على
تحليل للسبب الجذري مستخلص من فرق المصدر الأصلي، والتصحيحات المستخرجة، وأداة كشف
(بصمة إصدار سلبية) لتحديد عمليات التثبيت المتأثرة، ومختبر لإثبات الاستغلال.
الثغرة علنية وتم تصحيحها. يُنشر هذا العمل للاستخدام الدفاعي: مساعدة المشغّلين في العثور على المضيفين المتأثرين ومعالجتهم.
| نطاق الإصدار | الحالة |
|---|---|
< 1.13.0 | متأثر |
>= 1.13.0 | مُصحَّح |
الإصدار 1.13.0 (الصادر في 31.07.2026) هو أول إصدار مُصحَّح؛ وهو يصلح الحلقات الثلاث جميعها في السلسلة الموصوفة أدناه. راجع §1 من التحليل.
تتكوّن ثلاث نقاط ضعف مستقلة من إنشاء حساب مسؤول دون مصادقة:
1. تعارض في خريطة الصلاحيات — دمجت havePermissions() خريطتي صلاحيات باستخدام
array_merge(). كلتاهما تستخدم المفتاح النصي PPS_USERLEVELS ('userlevels')،
وarray_merge() تستبدل المفاتيح النصية، لذا استبدلت القائمة الافتراضية القصيرة
للمتحكّم الأساسي قائمة وحدة النوافذ المنبثقة بالكامل — مما أزال بصمت قيد المسؤول من
save وتسع دوال أخرى:
$permissions = $mod->getController()->getPermissions(); // [... 'save' ...]
$permissionsBase = $mod->getController()->getBasePermissions(); // ['getListForTbl','removeGroup','clear']
$permissions = array_merge($permissions, $permissionsBase); // base wins — 'save' is gone
يفشل الفحص مفتوحًا: الإجراء الغائب عن الخريطة لا يُرفض أبدًا.
2. nonce قابل لإعادة الاستخدام يُرسل بالبريد إلى الغرباء — كان save لا يزال
يتطلب pps_nonce، لكن رسالة تأكيد الاشتراك كانت تتضمّن إجراء الـ nonce نفسه. بالنسبة
للمستخدمين غير المسجّلين، يُربط nonce ووردبريس بـ uid=0، لذا فإن الرمز المُنشأ لمشترك
مجهول يتحقق لصالح أي مهاجم مجهول.
3. لا توجد قائمة أدوار مسموحة من جهة الخادم — مرّرت createWpSubscriber() الدور
المُهيّأ مباشرة إلى WP_User::set_role(). أما قائمة الأدوار الآمنة في الإضافة (التي
تستثني administrator) فكانت تُطبَّق فقط عند عرض القائمة المنسدلة في لوحة الإدارة —
وهو تحكّم في الواجهة فقط.
عند التسلسل: اشترك للحصول على nonce → أعد استخدامه ضد popup::save لتعيين
sub_wp_create_user_role=administrator → شغّل تدفق الاشتراك → حساب مسؤول دائم.
الشرح الكامل مع مراجع الملفات والأسطر: docs/ANALYSIS.md.
تحدّد poc/cve_2026_18322_check.py ما إذا كان الموقع يشغّل إصدارًا متأثرًا. وهي لا
تحاول الاستغلال أبدًا: بل تُصدر طلبات HTTP GET عادية وتقرأ الإصدار الذي ينشره
التثبيت عن نفسه.
git clone <this-repo> && cd CVE-2026-18322
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
requests موصى بها لكنها اختيارية — إذ تتراجع الأداة إلى المكتبة القياسية.
# Single target
python3 poc/cve_2026_18322_check.py https://example.com
# With supporting evidence
python3 poc/cve_2026_18322_check.py https://example.com -v
# Many targets, concurrently, exporting results
python3 poc/cve_2026_18322_check.py -f targets.txt -t 20 --json results.json
python3 poc/cve_2026_18322_check.py -f targets.txt --csv results.csv --only-vulnerable
# Through a proxy, ignoring TLS errors (lab use)
python3 poc/cve_2026_18322_check.py https://staging.internal -k --proxy http://127.0.0.1:8080
يمكن تمرير الأهداف كوسائط أو عبر -f (حيث يقرأ - من stdin). ويُفترض أن اسم المضيف
المجرد هو https://.
$ python3 poc/cve_2026_18322_check.py https://example.com -v
[VULNERABLE] https://example.com/ (Smart Popup by Supsystic 1.11.2) < 1.13.0 affected by CVE-2026-18322; upgrade to 1.13.0+
- asset-version: plugin asset enqueued with ?ver=1.11.2 (https://example.com/)
- homepage-reference: references /plugins/popup-by-supsystic/ (https://example.com/)
- readme: plugin readme.txt is publicly readable (https://example.com/wp-content/plugins/popup-by-supsystic/readme.txt)
- readme-stable-tag: Stable tag: 1.11.2 (.../readme.txt)
| الحالة | المعنى |
|---|---|
VULNERABLE | تم كشف الإضافة عند < 1.13.0 |
NOT_VULNERABLE | تم كشف الإضافة عند >= 1.13.0 |
PLUGIN_DETECTED_VERSION_UNKNOWN | الإضافة موجودة، لكن الإصدار غير قابل للتحديد — تحقق يدويًا |
PLUGIN_NOT_DETECTED | لا دليل على وجود الإضافة (وليس دليلًا على غيابها) |
ERROR | الهدف غير قابل للوصول أو مشوّه |
رموز الخروج: 0 = لا أهداف متأثرة، 1 = هدف متأثر واحد على الأقل، 2 = خطأ في الاستخدام.
إشارتان سلبيتان، كلتاهما طلبات HTTP GET عادية لموارد عامة:
?ver=PPS_VERSION
(classes/frame.php:410,473)، لذا غالبًا ما تكشف الصفحة الرئيسية الإصدار الدقيق.readme.txt — حقل Stable tag: موثوق وله الأولوية على أصل مخزّن مؤقتًا قد
يكون قديمًا؛ ويعمل سجل التغييرات كبديل احتياطي.كما يكتشف فحص الصفحة الرئيسية أدلّة wp-content المُعاد تسميتها (مثل /app/ في
Bedrock) بحيث يتبع البحث عن readme التخطيط الفعلي للموقع.
أداة الكشف لا تحاول الاستغلال. لا يوجد هنا أي استغلال مسلّح لهذه الثغرة: الكود الوحيد الذي يشغّل السلسلة الكاملة مقيّد بشكل صارم بالمختبر المحلي (راجع أدناه).
يقوم الفاحص السلبي ببصمة الإصدار فقط. ولا يكشف HttpClient الخاص به أي فعل HTTP
غير GET — وهو مفروض بالتصميم ومؤكَّد في مجموعة الاختبارات، لذا لا يمكنه إصدار طلب
يغيّر الحالة حتى عن طريق الخطأ. وهو لا يستدعي popup::save أبدًا، ولا يرسل معامل
sub_wp_create_user_role، ولا يرسل نموذج اشتراك، ولا يشغّل رسالة تأكيد بالبريد، ولا
ينشئ أو يعدّل أي مستخدم أو نافذة منبثقة أو إعداد. إنه يقرأ موردين عامين — الصفحة
الرئيسية وreadme.txt — ولا شيء غير ذلك.
لهذا الأمان ثمن، يُذكر بصراحة: الحكم لا يكون أفضل من بيانات الإصدار الوصفية التي ينشرها المضيف. المقايضات وأنماط الفشل: §9.
lab/exploit_full_chain.py هو الجزء الوحيد من الكود هنا الذي ينفّذ الهجوم الفعلي،
وهو ينشئ مسؤول ووردبريس. ويُنشر حتى يمكن إعادة إنتاج التحليل بدلًا من تلقّيه
على الثقة — فادّعاء وجود خلل في التخويل لا يمكن إثباته هو مجرد تأكيد، وليس اكتشافًا.
وقد كُتب من أجل الحزمة القابلة للتخلص في lab/ ولا شيء غيرها: