
CVE-2026-63030 + CVE-2026-60137 - “wp2shell”: WordPress कोर में बिना प्रमाणीकरण के RCE
REST API बैच रूट कन्फ्यूजन (CVE-2026-63030) को एक
WP_Queryauthor__not_inSQL इंजेक्शन (CVE-2026-60137) के साथ जोड़कर → एक डिफ़ॉल्ट वर्डप्रेस इंस्टॉल पर प्री-ऑथ रिमोट कोड एक्ज़ीक्यूशन प्राप्त होता है।Adam Kues (Assetnote / Searchlight Cyber) द्वारा खोजा गया, 2026-07-17 को प्रकट। सलाह: GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf।
| चेन (अप्रमाणित RCE) | WordPress 6.9.0 - 6.9.4 और 7.0.0 - 7.0.1 |
| केवल SQLi (एक facilitating प्लगिन/थीम आवश्यक) | 6.8.0 - 6.8.5 |
| प्रभावित नहीं | ≤ 6.8 बैच कन्फ्यूजन के लिए; 6.9.5 / 7.0.2 / 7.1-beta2 (पैच किया गया) |
| पूर्व शर्तें | REST API सुलभ; कोई स्थायी ऑब्जेक्ट कैश नहीं (Redis/Memcached); ≥1 प्रकाशित पोस्ट |
| प्रमाणीकरण आवश्यक | कोई नहीं |
| प्रभाव | अप्रमाणित → नया व्यवस्थापक बनाना → कोड निष्पादन (SQLi प्रशासक हैश भी डंप करता है) |
https://github.com/user-attachments/assets/7f9cc52c-3f31-4339-9192-e31e506684f6
requests निर्भरता या टूटी हुई सुविधाओं के।shell बिना क्रेडेंशियल के single-post UNION कन्फ्यूजन के माध्यम से एक नकली WP_Post बनाता है, कस्टमाइज़र को एक नया व्यवस्थापक बनाने के लिए ब्रिज करता है (POST /wp/v2/users), लॉग इन करता है, और एक टोकन-गेटेड वेबशेल ड्रॉप करता है। SQLi प्रशासक-हैश डंप (read --preset users) को दूसरे, सत्यापित पथ के रूप में रखा गया है।block_cannot_read) जो प्राथमिक, गैर-विनाशकारी check के रूप में उपयोग किया जाता है।sqli) जो अन्य PoC में नहीं है।$wp$2y$ पासवर्ड हैश के लिए hashcat मोड (-m 35500)।wp2shell/
├── README.md ← आप यहाँ हैं
├── wp2shell.py ← एकीकृत PoC (एकल फ़ाइल, केवल stdlib, 0xsha द्वारा)
└── lab/ ← प्रतिलिपि प्रस्तुत करने योग्य Docker प्रयोगशालाएँ + विश्वसनीयता मैट्रिक्स
├── docker-compose.yml (डिफ़ॉल्ट 6.9.4 प्रयोगशाला)
├── docker-compose.matrix.yml (पैरामीटरयुक्त: कोई भी संस्करण × MySQL/MariaDB)
├── docker-compose.sqli.yml (6.8.3 "केवल SQLi" प्रयोगशाला)
├── matrix.sh (पूर्ण विश्वसनीयता मैट्रिक्स चलाता है)
└── sqli-only/facilitator.php (mu-प्लगिन: 6.8.x facilitating sink)
जिन छह सार्वजनिक PoCs से यह टूल लिया गया है वे यहाँ विक्रेता नहीं हैं; वे क्रेडिट में लिंक किए गए हैं।
नीचे दी गई सभी बातों की स्थानीय Docker प्रयोगशाला में पुष्टि की गई (देखें §4); जो दावे प्रयोगशाला में नहीं चलाए गए, उन्हें ऐसे चिह्नित किया गया है।
यह श्रृंखला दो स्वतंत्र बगों को जोड़ती है। लाइन नंबर वास्तविक WordPress 6.9.4 स्रोत ( wordpress:6.9.4-apache से निकाला गया ) से हैं।
author__not_in SQL इंजेक्शन (CVE-2026-60137)wp-includes/class-wp-query.php, WP_Query::get_posts():
2403 if ( ! empty( $query_vars['author__not_in'] ) ) {
2404 if ( is_array( $query_vars['author__not_in'] ) ) { // ← गार्ड केवल ARRAYS के लिए काम करता है
2405 $query_vars['author__not_in'] = array_unique( array_map( 'absint', $query_vars['author__not_in'] ) );
2406 sort( $query_vars['author__not_in'] );
2407 }
2408 $author__not_in = implode( ',', (array) $query_vars['author__not_in'] ); // ← स्ट्रिंग सीधे गुज़र जाती है
2409 $where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) "; // ← कच्चा इंटरपोलेशन
2410 } elseif ( ! empty( $query_vars['author__in'] ) ) {
...
2415 $author__in = implode( ',', array_map( 'absint', array_unique( (array) $query_vars['author__in'] ) ) ); // ← implode के अंदर absint
एक स्ट्रिंग author__not_in is_array() गार्ड (2404) को छोड़ देती है; implode(',', (array)"…") इसे अपरिवर्तित लौटाता है (2408) और इसे SQL में कच्चा जोड़ दिया जाता है (2409)। सहोदर author__in (2415) implode के अंदर array_map('absint', …) को पुनः लागू करता है और सुरक्षित है - वह लापता array_map ही बग है। मान ... post_author NOT IN (<value>) ... के रूप में आता है, इसलिए 0) <sql>-- - सूची को बंद करता है और SQL जोड़ता है।
वहाँ एक स्ट्रिंग प्राप्त करना कठिन हिस्सा है: REST पोस्ट एंडपॉइंट author_exclude → author__not_in (class-wp-rest-posts-controller.php:247) मैप करता है लेकिन इसे पूर्णांकों के 'type' => 'array' के रूप में घोषित करता है, इसलिए कोर एक स्ट्रिंग को बाध्य/अस्वीकार करता है:
GET /wp-json/wp/v2/posts?author_exclude=1) OR SLEEP(3)-- -
→ 400 "author_exclude[0] is not of type integer." (6.8.3 पर सत्यापित)
यही कारण है कि बग A अकेला केवल “facilitated” है। बग B 6.9+ पर सत्यापन के पीछे स्ट्रिंग को तस्करी करता है।
wp-includes/rest-api/class-wp-rest-server.php, serve_batch_request_v1():
1720 if ( false === $parsed_url ) {
1721 $requests[] = new WP_Error( 'parse_path_failed', … ); // एक खराब पथ $requests में WP_Error बन जाता है
1749 foreach ( $requests as $single_request ) {
1750 if ( is_wp_error( $single_request ) ) {
1752 $validation[] = $single_request; // ← $validation में धकेला गया …
1753 continue; // ← … लेकिन $matches को SKIP किया गया
1754 }
1757 $matches[] = $match; // ← $matches केवल मान्य अनुरोधों के लिए बढ़ता है
1825 foreach ( $requests as $i => $single_request ) { // $requests में स्थिति द्वारा अनुक्रमित
1841 $match = $matches[ $i ]; // ← $matches छोटा है → +1 शिफ्ट
1861 $result = $this->respond_to_request( $single_request, $route, $handler, $error );
एक WP_Error उप-अनुरोध को $validation[] (1752) में धकेला जाता है लेकिन $matches[] में नहीं (1753 पर continue 1757 को छोड़ देता है), इसलिए $matches कम हो जाता है और $matches[$i] (1841) अगले अनुरोध के हैंडलर को धारण करता है। अनुरोध i को अनुरोध i+1 के हैंडलर के साथ भेजा जाता है, अपने स्वयं के पैराम और अपने स्वयं के (पारित) सत्यापन निर्णय को लेकर।
प्रतिगमन मूल (6.8.3 → 6.9.4 अंतर पर सत्यापित): 6.8.3 में लूप हर अनुरोध के लिए $matches[] = $match जोड़ता है और खराब पथ पहले लूप में हटा दिए जाते हैं - सरणियाँ संरेखित रहती हैं, कोई डीसिंक नहीं। 6.9.0 के रीफ़ैक्टर ने शिफ्ट शुरू किया। यही कारण है कि 6.8.x “केवल SQLi” है और RCE श्रृंखला 6.9.0 से शुरू होती है।
पैच त्रुटि प्रविष्टियों के लिए भी $matches[] जोड़ता है, पुनः-प्रवेश को कठोर करता है, और author__not_in को एक आईडी-सूची सहायक के साथ पार्स करता है। (6.9.5 परीक्षण के समय Docker Hub पर नहीं था, इसलिए यह सलाह से है, प्रयोगशाला अंतर से नहीं।)
बैच स्कीमा केवल POST/PUT/PATCH/DELETE उप-अनुरोधों की अनुमति देता है, लेकिन पोस्ट get_items ( author_exclude sink) GET-केवल है, इसलिए कन्फ्यूजन दो बार नेस्ट किया गया है: