
गैर-विनाशकारी डिटेक्टर + wp2shell के लिए Docker लैब (CVE-2026-63030 REST /batch/v1 रूट कन्फ्यूजन + WordPress कोर 6.9.0-6.9.4 / 7.0.0-7.0.1 में CVE-2026-60137 author__not_in SQLi)
wp2shell के लिए एक स्व-निहित लैब, गैर-विनाशकारी डिटेक्टर, और पूर्ण प्री-ऑथ RCE प्रूफ-ऑफ-कॉन्सेप्ट — WordPress कोर में प्री-ऑथेंटिकेशन भेद्यता श्रृंखला:
| CVE | घटक | वर्ग | CVSS |
|---|---|---|---|
| CVE-2026-60137 | WP_Query::author__not_in | SQL इंजेक्शन (CWE-89) | 9.1 |
| CVE-2026-63030 | REST /batch/v1 रूट कन्फ्यूज़न | व्याख्या विरोधाभास (CWE-436) → RCE तक श्रृंखलाबद्ध | 7.5 |
प्रभावित: WordPress कोर 6.9.0–6.9.4 और 7.0.0–7.0.1 (केवल SQLi सिंक भी 6.8.0–6.8.5 को प्रभावित करता है)। 6.8.6 / 6.9.5 / 7.0.2 में ठीक किया गया। Adam Kues (Assetnote / Searchlight Cyber) द्वारा रिपोर्ट किया गया; SQLi का श्रेय TF1T, dtro, haongo को भी दिया गया। स्टॉक-डिफ़ॉल्ट RCE श्रृंखला (oEmbed → changeset → re-entry) Mustafa Can İPEKÇİ (nukedx) द्वारा।
पहले अपडेट करें। WordPress ने इसके लिए बाध्यकारी ऑटो-अपडेट जारी किए। यह रेपो आपको यह सत्यापित करने में मदद करने के लिए है कि आपका अपना परिवेश पैच किया गया है और बग को समझने के लिए — किसी पर हमला करने के लिए नहीं। देखें SECURITY.md।
हमेशा-सत्य प्रिमिटिव एक अप्रमाणित, बिना-प्लगइन, स्टॉक-कोर SQL इंजेक्शन है जो
पूर्ण डेटाबेस पठन (एडमिन पासवर्ड हैश, wp_options/wp_users में सब कुछ) प्रदान करता है। यह अकेले ही
9.1 का दर्जा दिलाता है और तत्काल पैचिंग की मांग करता है।
RCE वास्तविक है और स्टॉक-डिफ़ॉल्ट WordPress पर काम करता है — कोई FILE विशेषाधिकार नहीं, कोई स्थायी
ऑब्जेक्ट कैश नहीं, कोई प्लगइन नहीं, कोई गलत कॉन्फ़िगरेशन आवश्यक नहीं। श्रृंखला रीड-ओनली SQLi को
पंक्ति-जालसाज़ी प्रिमिटिव के रूप में उपयोग करती है (UNION ALL SELECT नकली wp_posts पंक्तियाँ इंजेक्ट करता है), फिर
उन जाली पंक्तियों को oEmbed कैशिंग के माध्यम से वास्तविक डेटाबेस लेखों में बदलने के लिए WordPress की अपनी
कंटेंट-रेंडरिंग पाइपलाइन का लाभ उठाती है। वहाँ से, changeset एलिवेशन और पुनः-प्रवेशी parse_request एडमिन
संदर्भ में चलते हैं, एक नया एडमिनिस्ट्रेटर खाता बनाते हैं — सब कुछ एक ही अप्रमाणित HTTP अनुरोध से।
POST /?rest_route=/batch/v1)1. Route confusion — double-nested batch desyncs $matches/$validation so a GET
/wp/v2/widgets runs under posts::get_items() (public), reaching
WP_Query's author__not_in with attacker-controlled input.
2. Row forgery — author__not_in is string-concatenated into SQL;
"1) AND 1=0 UNION ALL SELECT <23 cols> -- -" injects fake
WP_Post rows. per_page=-1 bypasses split_the_query (WP_Query
treats -1 as "no limit" → empty $limits → split=false →
full SELECT wp_posts.* → UNION columns match).
3. oEmbed write — forged posts carry [embed]<self-url>[/embed]; rendering via
context=view makes WordPress cache real oembed_cache posts in
the DB (turns read-only SQLi into writes with predictable IDs).
4. Elevation+re-entry — a forged customize_changeset (user_id = real admin) plus a
forged post_type=request row with parent loops drives an
in-process re-entrant parse_request in admin context.
5. Admin creation — POST /wp/v2/users in the same batch passes
current_user_can('create_users') → new administrator.
6. RCE — login → plugin webshell upload → command execution → cleanup.
नवीनता संयोजन में है, किसी एकल बग में नहीं — व्यक्तिगत गैजेट्स वैध
WordPress व्यवहार हैं। उनका नामकरण (Adam Kues के writeup के अनुसार) exploit() में नकली-पंक्ति ग्राफ़ को
पठनीय बनाता है और इंगित करता है कि एंट्री पॉइंट्स के पैच होने के बाद किस चीज़ का पुनः-ऑडिट करना है:
wp_update_post() के माध्यम से सुलह करता है और इन-मेमोरी post_type/post_status को प्राथमिकता देता है, जिससे एक
oembed_cache पंक्ति को वास्तविक post/customize_changeset में पुनः-टाइप किया जा सकता है।post_content को संरक्षित करने वाली चक्र-पहचान — wp_insert_post_parent फ़िल्टर पैरेंट
श्रृंखला को ट्रैवर्स करता है; पहचाने गए चक्र पर यह एक दूसरा wp_update_post() कॉल करता है जो पैरेंट को ठीक करता है बिना
post_content को ओवरराइड किए। यही महत्वपूर्ण कड़ी है: यह नकली changeset के दुर्भावनापूर्ण
post_content को बचाए रखने देता है। (यही कारण है कि नकली पंक्तियाँ स्व-/पारस्परिक-पैरेंट लूप का उपयोग करती हैं।)parse_request के माध्यम से हुक रीप्ले — पोस्ट प्रकाशित करने पर do_action("{$status}_{$type}") फ़ायर होता है;
post_status=parse / post_type=request वाली एक नकली पंक्ति parse_request को ट्रिगर करती है, जो
बैच पाइपलाइन को पुनः चलाती है जबकि changeset की अनुमानित-एडमिन पहचान (wp_set_current_user) अभी भी बनी रहती है।ये गैजेट्स पैच किए गए WordPress में भी मौजूद रहते हैं — केवल दो एंट्री पॉइंट्स (बैच डीसिंक +
author__not_in स्केलर बायपास) बंद किए गए थे। कोई भी नया प्रिमिटिव जो इन-मेमोरी पोस्ट कैश को नकली बनाता है या
oembed_cache पंक्ति लिखता है, वही एडमिन-टेकओवर टेल को पुनः सक्षम कर देगा।
सभी चार रेंडर-टाइम राइट प्रिमिटिव्स 7.0.1 के विरुद्ध लाइव पुष्टि किए गए — प्रत्येक को एक अप्रमाणित पोस्ट के रूप में नकली बनाया गया, बैच कन्फ्यूज़न के माध्यम से रेंडर किया गया, और परिणामी DB राइट को ब्लाइंड SQLi द्वारा सत्यापित किया गया:
| प्रिमिटिव | ट्रिगर मार्कअप | सिंक | अनुमानित पहचानकर्ता | सत्यापित |
|---|---|---|---|---|
| oembed | [embed]<url>[/embed] | wp_posts पंक्ति (oembed_cache) | post_name = md5(url+attrs) | पोस्ट ID बनाई गई |
| rss | wp:rss {feedURL} | wp_options साइट-ट्रांसिएंट | _site_transient_feed_<md5(url)> | option_id, 5192 B कैश्ड |
| navigation | wp:navigation | wp_posts पंक्ति (wp_navigation) | post_name = 'navigation' (निश्चित) | ID बनाई गई, slug navigation |
| calendar | wp:calendar | wp_options | wp_calendar_block_has_published_posts | option_id, मान '1' |
प्रत्येक परिणाम क्या सिद्ध करता है:
md5(url+serialize(attrs))) और वास्तविक ऑटो-इंक्रीमेंट ID वाली एक नई wp_posts पंक्ति। यह एकमात्र ऐसा है जो आपको
एकाधिक, ऑन-डिमांड, हमलावर-नामित पोस्ट पंक्तियाँ देता है — यही कारण है कि RCE श्रृंखला इसका उपयोग
नकली changeset/request ग्राफ़ को बैक करने के लिए करती है।_site_transient_feed_<md5(url)> पूरी तरह से
पूर्वानुमेय है, और संग्रहीत बाइट्स वह फ़ीड बॉडी हैं जो हमलावर का URL सर्व करता है — अर्थात्, हमलावर
कुंजी और मान दोनों को नियंत्रित करता है। यह एक wp_options राइट है (कोई पोस्ट ID नहीं), इसलिए यह एक option-poisoning
प्रिमिटिव है, न कि changeset बैकिंग के लिए ड्रॉप-इन।wp_navigation मौजूद है तो छोड़ देता है) और निश्चित slug के साथ, इसलिए यह अधिकतम एक
नकली ऑब्जेक्ट को बैक कर सकता है, oEmbed की N पंक्तियों के विपरीत।update_option पथ अप्रमाणित रूप से फ़ायर होता है, लेकिन option
नाम और उसका '1'/'0' मान निश्चित/DB-व्युत्पन्न हैं, इसलिए यह एक "राइट होता है" प्रदर्शन है जिसमें
कुंजी या मान पर हमलावर का कोई नियंत्रण नहीं है।