
CVE-2026-87902 के लिए PoC — WordPress पेज-टेम्पलेट रिज़ॉल्यूशन में अनधिकृत पाथ ट्रैवर्सल (स्थानीय PHP इन्क्लूज़न, सशर्त RCE) एक पिन किए गए कमज़ोर लैब के साथ
WordPress Core में डबल-एन्कोडेड pagename मान के माध्यम से अनऑथेंटिकेटेड लोकल PHP फ़ाइल इन्क्लूज़न — और, विशिष्ट डिप्लॉयमेंट स्थितियों के अंतर्गत, वेब-सर्वर अकाउंट के विशेषाधिकारों के साथ PHP कोड एक्ज़ीक्यूशन।
| CVE | CVE-2026-87902 |
| विक्रेता एडवाइज़री | GHSA-7hp8-65ch-5whp |
| राइट-अप | CVE-2026-87902: Critical WordPress file inclusion and conditional RCE |
| प्रभावित | WordPress Core 4.7.0 – 7.1.1 (एडवाइज़री की प्रति-ब्रांच रेंज के अनुसार प्रत्येक ब्रांच); 7.0.2 पर गतिशील रूप से पुनरुत्पादित |
| ठीक किया गया | 7.1.2 (7.1 ब्रांच), 7.0.6 (7.0 ब्रांच) और 4.7.37 तक प्रत्येक ब्रांच के लिए एक बैकपोर्ट (एडवाइज़री के अनुसार) |
| कमज़ोरियाँ | CWE-98 (include में फ़ाइलनाम का अनुचित नियंत्रण), CWE-22 / CWE-23 (पाथ ट्रैवर्सल) |
| CVSS v3.1 | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — 8.1 High |
| CVSS v4.0 (पूरक) | CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N — 9.2 Critical |
| ऑथेंटिकेशन | कोई नहीं — कोई अकाउंट, कुकी, नॉन्स, सेशन, प्लगइन, या आउटबाउंड रिक्वेस्ट नहीं |
| उपयोगकर्ता इंटरैक्शन | कोई नहीं |
| लेखक | Robert Ressl — ressl.ch |
| PoC सत्यापित | 2026-09-22 इस रिपॉज़िटरी की लैब के विरुद्ध — देखें सत्यापित परिणाम |
WordPress एक Page टेम्पलेट को ऐसी श्रृंखला के माध्यम से रिज़ॉल्व करता है जो कभी यह सिद्ध नहीं करती कि चयनित फ़ाइल किसी थीम रूट के भीतर ही रहती है:
pagename और page_id सार्वजनिक क्वेरी वेरिएबल हैं जिन्हें WP::parse_request() एक अनाम POST बॉडी से स्वीकार करता है।pagename में एक डबल-एन्कोडेड ट्रैवर्सल प्रारंभिक क्वेरी सैनिटाइज़ेशन से एस्केप्ड %xx ऑक्टेट के रूप में बच निकलता है (wp_basename() %2f को विभाजक के रूप में नहीं देख सकता, और sanitize_title_with_dashes() जानबूझकर वैध ऑक्टेट रखता है)।page_id एक वास्तविक प्रकाशित Page का चयन करता है, जबकि दुर्भावनापूर्ण pagename क्वेरी ऑब्जेक्ट में बना रहता है।get_page_template() बाद में उस मान पर urldecode() कॉल करता है और टेम्पलेट पदानुक्रम में page-templates/../../../../../../../usr/local/lib/php/pearcmd.php जैसा एक कैंडिडेट जोड़ता है।locate_template() और अंतिम टेम्पलेट लोडर केवल अस्तित्व, पठनीयता और .php/.html सफ़िक्स की जाँच करते हैं — कभी यह नहीं कि कैनोनिकल पाथ अभी भी किसी अनुमत थीम रूट के भीतर है — और फिर उसे include कर देते हैं।यह WordPress Core में एक अनऑथेंटिकेटेड रिमोट लोकल-फ़ाइल-इन्क्लूज़न प्रिमिटिव है। परीक्षण किए गए आधिकारिक रनटाइम (wordpress:php8.3-apache, जो PEAR बंडल करता है और कोई php.ini लोड नहीं करता, इसलिए register_argc_argv On है) पर, इन्क्लूज़न को PEAR के pearcmd.php के माध्यम से श्रृंखलाबद्ध किया गया: एक पहली अनाम रिक्वेस्ट config-create को /tmp में हमलावर-नियंत्रित PHP लिखने पर मजबूर करती है, दूसरी अनाम रिक्वेस्ट उस फ़ाइल को include करके उसे www-data के रूप में निष्पादित करती है।
Core में अनुपस्थित पाथ कंटेनमेंट ही भेद्यता है। PEAR इन्क्लूज़न से कोड एक्ज़ीक्यूशन तक का केवल एक पर्यावरण-निर्भर मार्ग है — यह WordPress की निर्भरता नहीं है और हर डिप्लॉयमेंट में मौजूद या उपयोगी नहीं है।
सत्यापित स्रोत स्थान (WordPress 7.0.2):
| # | स्थान | भूमिका |
|---|---|---|
| 1 | wp-includes/class-wp.php:18,322-330 | pagename और page_id सार्वजनिक क्वेरी वेरिएबल हैं और $_POST से पढ़े जाते हैं |
| 2 | wp-includes/class-wp-query.php:2205 | sanitize_title_for_query( wp_basename( $query_vars['pagename'] ) ) — wp_basename() के लिए %2f विभाजक नहीं है |
| 3 | wp-includes/formatting.php:2283-2289 | sanitize_title_with_dashes() वैध %xx ऑक्टेट को हटाने के बजाय संरक्षित रखता है |
| 4 | wp-includes/template.php:492 | $pagename_decoded = urldecode( $pagename ); — ट्रैवर्सल सैनिटाइज़ेशन के बाद सक्रिय हो जाता है |
| 5 | wp-includes/template.php:722-736 | locate_template() कैंडिडेट को प्रत्येक थीम रूट के नीचे जोड़ता है और केवल file_exists() कॉल करता है |
| 6 | wp-includes/template-loader.php:116-132 | realpath() पाथ को सामान्यीकृत करता है, फिर include बिना किसी कैनोनिकल रूट-कंटेनमेंट जाँच के चलता है |
| 7 | wp-includes/canonical.php:42-47 | redirect_canonical() गैर-GET/HEAD के लिए जल्दी लौट आता है, इसलिए एक POST कैनोनिकलाइज़ होकर हटाया नहीं जाता |
स्टैंडअलोन इन्क्लूज़न प्रिमिटिव को चाहिए:
| # | पूर्वापेक्षा | कारण |
|---|---|---|
| 1 | संख्यात्मक page_id द्वारा चयनित एक प्रकाशित, अनाम रूप से पहुँचने योग्य Page | पाथनेम लुकअप विफल होने के बाद क्वेरी द्वारा पेज लौटाया जाना चाहिए |
| 2 | कोई पहले से हल होने योग्य कस्टम पेज टेम्पलेट नहीं | एक वैध असाइन किया गया टेम्पलेट दुर्भावनापूर्ण कैंडिडेट से पहले क्रमित होता है |
| 3 | सक्रिय (चाइल्ड या पैरेंट) थीम में एक टॉप-लेवल डायरेक्टरी जिसका नाम page- से शुरू हो, जैसे page-templates/ | WordPress निश्चित page- प्रीफ़िक्स जोड़ता है, इसलिए .. ट्रैवर्सल स्थान 0 से शुरू नहीं हो सकता; डायरेक्टरी का केवल मौजूद और ट्रैवर्सेबल होना ज़रूरी है — उसका राइटेबल होना ज़रूरी नहीं |
| 4 | एक चयनित लोकल .php फ़ाइल जो मौजूद हो और PHP उपयोगकर्ता द्वारा पठनीय हो | लोडर is_file()/is_readable() जाँचता है और .php सफ़िक्स आवश्यक है |
| 5 | उस फ़ाइल को अवरुद्ध करने वाला कोई फ़ाइलसिस्टम कंफ़ाइनमेंट न हो | एक open_basedir या MAC नीति include को रोक सकती है |
प्रदर्शित PEAR चरण को अतिरिक्त रूप से एक पठनीय pearcmd.php (उसकी निर्भरताओं सहित), वेब SAPI के लिए register_argc_argv=On, और एक राइटेबल आउटपुट डायरेक्टरी चाहिए। प्रोडक्शन php.ini फ़ाइलें register_argc_argv=Off सेट करती हैं; परीक्षित इमेज कोई php.ini लोड नहीं करती, इसलिए कंपाइल्ड डिफ़ॉल्ट (On) लागू हुआ। यह प्रदर्शित कोड-एक्ज़ीक्यूशन श्रृंखला की व्यापकता पर एक महत्वपूर्ण सीमा है।
बंडल किए गए Twenty Twenty-Three/Four/Five थीम कोई टॉप-लेवल page-* डायरेक्टरी नहीं देते, इसलिए स्टॉक लैब को नीचे वर्णित फ़िक्स्चर चाहिए। कस्टम थीम वैध रूप से page-templates/ लेआउट का उपयोग कर सकती हैं (WordPress दस्तावेज़ीकरण)।
एडवाइज़री दर्ज करती है कि ये शर्तें शिप किए गए सॉफ़्टवेयर में कहाँ होती हैं: थीम शर्त लीगेसी Twenty Twelve और Twenty Fourteen थीम तथा Neve, Hestia और Sydney जैसी तृतीय-पक्ष थीम द्वारा पूरी होती है, जबकि PEAR ट्रांज़िशन आधिकारिक PHP Docker इमेज और PHP 8.5 से पुराने PHP चलाने वाले डिफ़ॉल्ट cPanel कॉन्फ़िगरेशन पर लागू होता है। (Twenty Twelve और Twenty Fourteen दोनों एक टॉप-लेवल page-templates/ डायरेक्टरी देते हैं; शेष कथन एडवाइज़री के हैं।) यह रिपॉज़िटरी यह नहीं मापती कि पूरी श्रृंखला कितनी बार लागू होती है।
./lab/up.sh # WordPress 7.0.2 + MySQL 8.4 + the page-* fixture, installed and ready
python3 cve-2026-87902.py # two anonymous POSTs, prints the proof marker
cve-2026-87902.py को Python 3.6+ चाहिए (केवल स्टैंडर्ड लाइब्रेरी) और डिफ़ॉल्ट रूप से लैब तक http://127.0.0.1:8091 पर पहुँचता है। अपेक्षित आउटपुट:
[*] target : http://127.0.0.1:8091
[*] page id : 2 (sample-page, default template, via /index.php?rest_route=/wp/v2/pages&per_page=100&_fields=id,slug,template)
[*] depth 7 : stage 1 HTTP 200, stage 2 HTTP 200
[+] traversal : page-templates/../../../../../../../usr/local/lib/php/pearcmd.php
[+] payload file : page-templates/../../../../../../../tmp/wp-pear-rce-flag.php (written by PEAR in stage 1)
[+] marker : 'CVE-2026-87902-POC-OK' found 12 time(s) in the stage-2 response
[+] proof : CVE-2026-87902-POC-OK
[+] EXPLOIT SUCCESSFUL - PHP executed with the web-server account's privileges
सफलता पर एग्ज़िट कोड 0 और अन्यथा 1 होता है, इसलिए PoC एक रिग्रेशन/डिटेक्शन जाँच के रूप में भी काम करता है।
टियरडाउन: docker compose down -v.
2026-09-22 को इस लैब के विरुद्ध चलाया गया (Docker 29.4, OrbStack, Apple silicon):