Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
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
112il 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 :

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()) :

$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 :

// 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 :

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) :

$_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 :

$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 ... :

Télécharger l’outil