
एक WordPress प्लगइन जो REST API के माध्यम से एक MCP सर्वर को उजागर करता है, जिसमें सुरक्षा मॉडल ही मुख्य बिंदु है -- CVE-2026-15015 OAuth-बायपास स्वरूप को इस तरह बंद करता है कि वह सतह कभी होती ही नहीं।
सरल शब्दों में: यह एक AI एजेंट को एक वास्तविक WordPress Application Password के माध्यम से एक WordPress साइट को पढ़ने और हल्के से लिखने की अनुमति देता है — वही क्रेडेंशियल प्रकार जो wp-admin पहले से जारी करता है — और कुछ नहीं। यहाँ कोई साइन-अप फ़ॉर्म नहीं है, कोई OAuth क्लाइंट पंजीकरण नहीं है, किसी भी प्रकार का कोई टोकन एंडपॉइंट नहीं है। इसी क्षेत्र में एक प्लगइन ने ठीक यही शिप किया था, और इसी तरह एक अनप्रमाणित हमलावर पूर्ण एडमिन एक्सेस लेकर चला गया।
एक WordPress प्लगइन जो REST API पर एक MCP सर्वर को उजागर करता है, जिसमें सुरक्षा मॉडल ही मुख्य बिंदु है, कोई फ़ीचर बुलेट नहीं।
CVE-2026-15015, CVSS 9.8: WordPress के लिए MountDev AI MCP Connector ने एक सार्वजनिक रूप से सुलभ OAuth 2.1 Dynamic Client Registration एंडपॉइंट के साथ-साथ एक authorization एंडपॉइंट भी उजागर किया जो यह भी नहीं जाँचता था कि कौन पूछ रहा है। एक हमलावर अपना खुद का OAuth क्लाइंट पंजीकृत करता है — किसी क्रेडेंशियल की आवश्यकता नहीं, एंडपॉइंट डिज़ाइन से ही अनप्रमाणित है, "dynamic client registration" का यही अर्थ है — इसे बिना किसी की निगरानी के authorization एंडपॉइंट से गुज़ारता है, और एक administrator खाते से बंधा bearer token प्राप्त करता है। पूर्ण साइट नियंत्रण: सामग्री, उपयोगकर्ता, सेटिंग्स। कुछ भी गलत कॉन्फ़िगर नहीं था; प्रवाह ठीक वैसे ही काम कर रहा था जैसे OAuth क्लाइंट पंजीकरण को करना चाहिए। बस इसे कभी भी उस समय सुलभ नहीं होना चाहिए था जब इसकी स्वीकृति के लिए पहले से कोई एडमिन मौजूद न हो।
सामान्यीकृत करने वाला समाधान "OAuth को और सावधानी से लागू करना" नहीं है। यह वह सतह न होना है। यह प्लगइन किसी भी प्रकार का कोई client-registration एंडपॉइंट, कोई authorization एंडपॉइंट, और कोई token-issuance एंडपॉइंट पंजीकृत नहीं करता। हमलावर के पास पंजीकरण करने के लिए कुछ नहीं है, क्योंकि यहाँ ऐसा कुछ भी नहीं है जो क्रेडेंशियल बाँटता हो। प्रमाणीकरण एक WordPress Application Password है जिसे एक administrator ने पहले ही wp-admin से एक विशिष्ट उपयोगकर्ता के लिए बनाया है — एक क्रेडेंशियल जो केवल इसलिए मौजूद है क्योंकि manage_options वाले एक मानव ने इसे बनाने का चुनाव किया, पहले से, इस प्लगइन से पूरी तरह से बाहर।
इनमें से कोई भी अकेले नई नहीं है; दोनों को, जानबूझकर, एक-दूसरे से स्वतंत्र रूप से चलाना, वही है जो किसी एक में हुई गलती को बंद कर देता है:
1. केवल Application Password — कभी cookie session नहीं। WordPress का अपना is_user_logged_in() एक वैध cookie और nonce रखने वाले ब्राउज़र टैब के लिए भी true होता है। Auth::permission_callback() उस पथ को जानबूझकर अस्वीकार करता है: यह उस application_password_did_authenticate action को सुनता है जिसे WordPress core विशेष रूप से तब फ़ायर करता है जब Basic-auth Application Password प्रमाणीकरण सफल होता है, और उस फ़्लैग की आवश्यकता रखता है, न कि केवल "कोई उपयोगकर्ता लॉग इन है।" एक MCP क्लाइंट nonce वाला ब्राउज़र टैब नहीं है; यहाँ cookie auth स्वीकार करना एक ऐसी टूल सतह में CSRF-आकार का पथ खोल देगा जिसे अंग्रेज़ी में सामग्री संशोधित करने के लिए कहा जा सकता है, और इससे इस प्लगइन को जिस एक दरवाज़े की वास्तव में आवश्यकता है उस पर कोई लाभ नहीं मिलेगा।
2. प्रति-टूल capability, साथ ही एक write gate जिसे अकेला capability खोल नहीं सकता। ToolRegistry::call() हर कॉल पर user_can($user_id, $capability) को नए सिरे से जाँचता है — पोस्ट के लिए edit_posts, ऑर्डर के लिए manage_woocommerce। अलग से, एकमात्र write टूल, create_draft_post, को अतिरिक्त रूप से आवश्यकता होती है कि एक administrator ने Settings → WP Secure MCP से opt in किया हो, जो डिफ़ॉल्ट रूप से बंद है। एक Application Password जो संयोग से edit_posts रखता है, तब तक कुछ भी नहीं लिख सकता जब तक किसी मानव ने वह स्विच फ़्लिप न किया हो — एक capability जाँच और एक साइट-व्यापी gate दो अलग ताले हैं, और परीक्षण सिद्ध करते हैं कि किसी भी दिशा में एक भी दूसरे का विकल्प नहीं है।
limit, 1–20) सीमित करते हैं कि एक कॉल कितना लौटा सकती है; एक वैध रूप से महँगी WP_Query या wc_get_orders कॉल की लागत वही रहती है जो वह है।| Tool | Capability | Read-only | Notes |
|---|---|---|---|
search_posts | edit_posts | Yes | Keyword search. Hardcoded to post_status=publish — never returns drafts or private posts, even to a caller who could see them in wp-admin. |
get_post | edit_posts | Yes | Fetch by ID. Refuses a real post at a real ID if its status isn't publish — an ID is not a bypass around the same rule search_posts enforces. |
list_recent_orders | manage_woocommerce | Yes | Status, total, currency, date only. Never customer name, email, or address — order visibility doesn't require handing an agent a customer's PII, capability check or not. |
create_draft_post | edit_posts | No | post_status is a literal 'draft' in the source, not an argument. This tool cannot publish under any input, even with writes enabled. Off site-wide until an administrator opts in. |
हर टूल की tools/list प्रविष्टि में readOnlyHint / destructiveHint एनोटेशन होते हैं, ताकि एक क्लाइंट यह तय कर सके कि किसी को कॉल करने से पहले किसी मानव से पूछना है या नहीं, बिना यह पहले से जाने कि वह क्या करता है।
composer install --no-dev
प्लगइन निर्देशिका को wp-content/plugins/ में कॉपी करें (या symlink करें), इसे wp-admin से सक्रिय करें, फिर उस खाते के लिए एक Application Password बनाएँ जिसके रूप में एजेंट को कार्य करना चाहिए: Users → your profile → Application Passwords।
Writes डिफ़ॉल्ट रूप से बंद हैं। create_draft_post की अनुमति देने के लिए: Settings → WP Secure MCP → Write tools। इसके ऊपर प्रति-टूल capability जाँच अभी भी लागू होती है — साइट-व्यापी स्विच फ़्लिप करने से किसी को वह capability नहीं मिल जाती जो उसके पास पहले से नहीं थी।
55 परीक्षण, सभी शुद्ध — कोई WordPress इंस्टॉल नहीं, कोई डेटाबेस नहीं, कोई Docker नहीं। tests/bootstrap.php उन WordPress फ़ंक्शनों को stub करता है जिन्हें src/ कॉल करता है (current_user_can, get_post, wp_insert_post, ...), ठीक उसी तरह जैसे एक WordPress प्लगइन को पारंपरिक रूप से WordPress को लोड किए बिना unit-test किया जाता है।
composer install
vendor/bin/phpunit --testdox
सबसे भारी कवरेज वहीं बैठता है जहाँ यह सबसे अधिक मायने रखता है: