
Modern Events Calendar Lite <= 7.33.0 — अनधिकृत SQL Injection
mec_list_load_more (atts[include] / atts[exclude]) के माध्यम से अनधिकृत SQL इंजेक्शन| विवरण | मान |
|---|---|
| प्लगइन | 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