
استغلال 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) على الخادم.
| السمة | القيمة |
|---|---|
| معرّف 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) |
تستخدم 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().