Skip to content
KitploitKITPLOIT
HerramientasBlog
Log in
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Herramientas/GitHubGitHub/hann1bl3l3ct3r/cve-2026-11349
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebSeguridad WebPruebas de PenetraciónSeguridad de Bases de Datos
GitHubhann1bl3l3ct3r/cve-2026-11349

CVE-2026-11349

Modern Events Calendar Lite <= 7.33.0 — Inyección SQL no autenticada

Ver Repositorio
112hace 3 mesesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Modern Events Calendar Lite <= 7.33.0 — Inyección SQL no autenticada mediante mec_list_load_more (atts[include] / atts[exclude])

Resumen

DetalleValor
PluginModern Events Calendar Lite
Slugmodern-events-calendar-lite
AutorWebnus
Afectado<= 7.33.0 (la versión Lite actual distribuida por el proveedor). El fallo está presente en todo el rango posterior a w.org; confirmado en laboratorio en 6.5.6 y 7.33.0, confirmado estáticamente en 5.21.2. 6.5.6 = última compilación de wordpress.org (congelada en el cierre del 2022-05-11); 7.33.0 = compilación actual distribuida desde mec.webnus.net
Instalaciones activasConteo de wordpress.org oculto desde el cierre; históricamente más de 100.000. Aún distribuida/actualizada activamente por el proveedor (Lite vía mec.webnus.net; la misma base de código 7.x subyace al MEC Pro vendido activamente)
CWECWE-89 (Inyección SQL)
VulnerabilidadInyección SQL ciega no autenticada (basada en tiempo / booleana / en errores)
Privilegio requeridoNinguno (wp_ajax_nopriv_* — pre-autenticación)
Interacción del usuarioNinguna
CVSS v3.17.5 (Alta) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
EstadoVerificado en laboratorio de extremo a extremo en 7.33.0 (actual) y 6.5.6 (WordPress 6.6.5, MariaDB 10.x)
CVE / GHSACVE-2026-11349

Descripción

Modern Events Calendar Lite registra una familia de acciones "load more" no autenticadas en admin-ajax.php para sus skins de listado de eventos (list, grid, masonry, agenda, timeline, tile, custom). Cada manejador lee el array de solicitud atts controlado por el atacante, lo pasa a través de un helper llamado sanitize_deep_array() que — cuando se llama con sus argumentos por defecto — no realiza ninguna sanitización — y luego concatena los valores de atts['include'] (y atts['exclude']) crudos dentro de un fragmento SQL post_id IN (...) que se ejecuta con $wpdb->get_results() y sin $wpdb->prepare().

Dado que los puntos de entrada están registrados en wp_ajax_nopriv_*, no se requiere autenticación, cuenta, nonce ni interacción del usuario. Un atacante remoto no autenticado puede inyectar SQL arbitrario en la cláusula WHERE de un SELECT contra wp_mec_dates y leer cualquier dato en la base de datos de WordPress (hashes de contraseñas de usuarios, secretos/claves de wp_options, datos de otros plugins) mediante técnicas ciegas basadas en tiempo / booleanas / en errores.


Causa raíz

1. Un "sanitizador" que no sanitiza nada en la ruta por defecto

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;
}

El guard (is_array($excludes) and !count($excludes)) convierte la función en una operación nula completa cuando $excludes es el array vacío por defecto — cada valor se copia tal cual. La intención era evidentemente "si hay una lista de exclusión, omitir esas claves"; la lógica booleana, en cambio, omite todo cuando no se proporciona ninguna lista de exclusión.

2. El llamador no proporciona $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']) se llama con un único argumento → $excludes toma por defecto array() → se ejecuta la rama de operación nula anterior → $atts es el $_REQUEST['atts'] crudo y no confiable.

3. Concatenación cruda dentro de la cláusula 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']).")";

Los elementos del array se pasan directamente a implode() dentro de la cadena SQL sin conversión a entero ni escape. (absint()/(int) en cada elemento habría cerrado esto.)

4. Ejecución sin sentencia preparada

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 cadena completamente construida se entrega tal cual a $wpdb->get_results().

Flujo de la contaminación (solicitud → sumidero):

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

Accesibilidad

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

El registro nopriv hace que el endpoint sea alcanzable antes de la autenticación. La misma forma de load_more() y el constructor de consultas compartido en skins.php están presentes en los skins hermanos, cada uno con su propia acción wp_ajax_nopriv_*, por lo que la misma inyección es alcanzable a través de cualquiera de:

Acción AJAX (nopriv)Skin manejador
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

No se comprueba ningún nonce en load_more(), y la acción no requiere que haya ningún shortcode del plugin en una página — los manejadores AJAX se registran incondicionalmente en el init.


Prueba de Concepto (verificada en laboratorio, MEC Lite 6.5.6, WordPress 6.6.5, MariaDB 10.x)

Todas las solicitudes son no autenticadas (sin cookie, sin nonce). El valor inyectado se coloca en atts[include][]; el payload cierra los dos paréntesis abiertos del grupo IN ((...) AND (... IN ( y agrega un OR <sleep> de nivel superior para que la condición se evalúe para cada fila escaneada, y luego comenta el )) ORDER BY ... final:

TARGET='https://victim.example'          # plain permalinks: use admin-ajax.php directly
Descargar herramienta