GravCMS组合拳漏洞|CVE-2026-42613&CVE-2026-42607复现&研究

admin 2026-05-20 05:12:57 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细分析了GravCMS在2.0.0-beta.2之前版本存在的两个高危漏洞组合利用链。CVE-2026-42613为权限提升漏洞,攻击者可通过注册功能直接创建管理员账户;CVE-2026-42607为远程代码执行漏洞,管理员上传恶意ZIP插件包可导致服务器被控制。文档提供了完整的漏洞复现步骤、流量特征分析,并深入剖析了Installer模块解压逻辑缺陷和install.php自动加载机制等核心漏洞原理。建议用户及时升级到安全版本并加强文件上传验证。 综合评分: 85 文章分类: 漏洞分析,WEB安全,应用安全,安全工具,渗透测试


cover_image

Grav CMS 组合拳漏洞| CVE-2026-42613&CVE-2026-42607复现&研究

原创

404号浪漫 404号浪漫

404号浪漫

2026年5月12日 22:14 北京

在小说阅读器读本章

去阅读

点击蓝字,关注我们

0x0 背景介绍

Grav是一个基于文件的Web平台。

在2.0.0-beta.2之前版本中,存在两个高危漏洞可导致组合利用
1. 权限提升漏洞->CVE-2026-42613
Grav的Login插件在处理用户注册请求时,未对请求数据中的groups/access字段进行服务端校验。当管理员在插件配置中启用注册功能,并将groups/access添加为注册表单的可用字段时,未认证的攻击者可通过构造带有恶意groups/access参数的注册请求,攻击者无需任何凭据直接创建出拥有超级管理员权限的账户,实现未授权权限提升
2. 远程代码执行漏洞->CVE-2026-42607
Grav管理后台的“直接安装”功能在解压用户上传的ZIP插件包时,未对包内的文件类型和内容做任何安全检查,直接调用Installer::install,后者仅使用ZipArchive::extractTo进行解压)。拥有管理员权限的攻击者可以上传一个包含恶意PHP代码的合法结构插件ZIP包,其中的PHP文件会随Grav的插件初始化事件自动执行,进而在服务器上写入Webshell或执行任意系统命令,最终获得服务器控制权。

漏洞详情

| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | 未授权操作 代码执行 | < 2.0.0-beta.2 | 低 | CVE-2026-42613 CVE-2026-42607 |

攻击效果:

  • 未授权创建管理用户配合上传恶意ZIP可导致RCE。

0x2 漏洞复现

前置条件

  • 管理员启用了用户注册,且将groups、access加入注册表单字段
  • 安装并启用Admin插件;攻击者持有 管理员 凭据(或等效会话)

2.1-脚本验证

  • 两个单独的脚本,可以先创建用户再执行RCE
https://github.com/Kai-One001/cve-/blob/main/Grav_CMS_login_CVE-2026-42613.pyhttps://github.com/Kai-One001/cve-/blob/main/Grav_CMS_RCE_CVE-2026-42607.py

2.2-复现流量特征 (PCAP)

https://github.com/Kai-One001/PCAP-For-Cybersecurity.rule/blob/main/2026/Grav_CMS_RCE_CVE-2026-42613%26CVE-2026-42607.pcap
  • 挑取部分的截图

  • 明文可见项目结构

  • 后续Shell(没忍住shell了)


0x3 漏洞原理分析

3.0-架构与模块定位

本次分析是Grav v1.7.52核心发行包,没有看user/plugins/admin,因此下文代码级实证以当前工作区可核对的为主。

Grav的包管理分为三层:管理后台暴露HTTP入口;Grav Package Managersystem核心中完成下载、解压与安装编排;Installer作为底层驱动,封装ZipArchive 解压、目标目录校验以及install.php 钩子加载,了解这些思路就清晰了。

| 层级 | 核心文件(职责) | 在漏洞链中的角色 | | — | — | — | | 入口层 | Admin 插件:AdminController.phpdirectInstall 任务,Gpm.php;路由 /admin/tools/direct-install | 接收multipart 上传的 ZIP、校验会话与 admin-nonce,将文件交给GPM/Installer | | 逻辑层 | system/src/Grav/Common/GPM/GPM.phpcopyPackagegetPackageTypegetPackageName 等) | 识别包类型(plugin/theme/grav)、解析安装路径 | | 逻辑层 / 驱动层 | system/src/Grav/Common/GPM/Installer.php(unZip、install、loadInstaller) | 解压 ZIP → 移动/复制 user/plugins 或 user/themes → require_once 包内 install.php | | 入口层(CLI,同源逻辑) | system/src/Grav/Console/Gpm/DirectInstallCommand.php | 与 Web 共用 Installer::unZip / Installer::install;Grav 整包升级路径下 include 解压出的 system/install.php |

#

3.1-锁定关键路径

从管理员选择的 ZIP 文件出发,数据流在核心层必然经过Installer::unZip:这里使用ZipArchive::open 与extractTo整包解压,解压结果路径由ZIP内第一条目录名与临时目录拼接得到,随后Installer::install 在未使用sophisticated模式时对插件默认走moveInstall,将整个解压出的目录树迁入user/plugins/<slug>/(或主题目录)。

再往后,loadInstaller如果发现包根下存在install.php,会require_once 该文件并解析约定类名的安装器类——这意味着 ZIP 内的任意 PHP 在解压后、安装流程中即可被解释器加载执行,与禁止直接上传 .php的假设形成断裂。

3.2 [核心入口] 先看「流量落点」再看「Installer」

废话不多说,我们需要找到 谁把不可信数据交给了解压器。漏洞描述可知将Web入口指到/admin/tools/direct-install 的directInstall任务。顺着这个接口往下追,Admin插件最终会调用核心里的Installer::install()。因此,即使当前工作区没有Admin源码,只要Grav核心Installer对ZIP采取「先解压、再信任」的策略,任何复用该API的入口(Admin、未来其他插件、甚至误暴露的封装)都会继承同一风险。

3.3-[逻辑缺陷] unZip:解压即信任

全局找定位到system/src/Grav/Common/GPM/Installer.php,是因为所有 GPM安装路径都要把包落到临时目录再迁入user/;DirectInstallCommand里也能看到显式调用Installer::unZip($zip, $tmp_source)。

进unZip后,可以看到打开归档后直接extractTo,没有在循环里对$zip->getNameIndex($i)做安全判定——Zip Slip与包内任意文件两类问题在解压这都没有被拦

// system/src/Grav/Common/GPM/Installer.php&nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;function&nbsp;unZip($zip_file,&nbsp;$destination)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$zip&nbsp;=&nbsp;new&nbsp;ZipArchive();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$archive&nbsp;=&nbsp;$zip->open($zip_file);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($archive&nbsp;===&nbsp;true) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Folder::create($destination);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$unzip&nbsp;=&nbsp;$zip->extractTo($destination);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!$unzip) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self::$error&nbsp;=&nbsp;self::ZIP_EXTRACT_ERROR;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Folder::delete($destination);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$zip->close();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$package_folder_name&nbsp;=&nbsp;$zip->getNameIndex(0);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$zip->close();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;$destination&nbsp;.&nbsp;'/'&nbsp;.&nbsp;$package_folder_name;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;false;&nbsp; &nbsp; }
  • 这里的设计假设等价于ZIP与其中所有相对路径都是可信的
  • extractTo在PHP层会按条目名写盘,外层禁止.php上传无法约束压缩包内evil.php、../../html/shell.php``的检查

3.4-[逻辑缺陷] loadInstallerrequire_once 包内 install.php

接着往下看Installer::install,在文件复制/移动之前会调用loadInstaller。若根存在install.php,核心会require_once相当于把是否执行这段PHP的裁决权交给了ZIP作者。

// system/src/Grav/Common/GPM/Installer.php&nbsp; &nbsp;&nbsp;private&nbsp;static function loadInstaller($installer_file_folder,&nbsp;$is_install)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$installer_file_folder&nbsp;=&nbsp;rtrim($installer_file_folder, DS);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$install_file&nbsp;=&nbsp;$installer_file_folder&nbsp;. DS .&nbsp;'install.php';
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!file_exists($install_file)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;null;&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;require_once&nbsp;$install_file;
  • 很多CMS把install.php当作可信物里的安装钩子
  • 但在直接安装 = 接受任意ZIP的语义下,这一钩子变成了 即时代码执行点
  • 即便主插件PHP尚未被路由访问,require_once已在服务端完成一次解释执行,攻击面比仅写盘、待访问更靠前。

3.5-[攻击子链] Grav 整包升级路径中的 include

CLI DirectInstallCommand::upgradeGrav对类型为grav的包会include解压目录下的system/install.php并执行返回的callable。

// system/src/Grav/Console/Gpm/DirectInstallCommand.php&nbsp; &nbsp;&nbsp;private&nbsp;function upgradeGrav(string&nbsp;$zip,&nbsp;string&nbsp;$folder):&nbsp;void&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!is_dir($folder)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Installer::setError('Invalid source folder');&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$script&nbsp;=&nbsp;$folder&nbsp;.&nbsp;'/system/install.php';&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/** Install $installer */&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;((file_exists($script) &&&nbsp;$install&nbsp;=&nbsp;include&nbsp;$script) &&&nbsp;is_callable($install)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$install($zip);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;throw&nbsp;new RuntimeException('Uploaded archive file is not a valid Grav update package');&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

这条链路常需要 能执行CLI的权限,与HTTP Admin的威胁模型不太一样,但说明执行解压产物中的PHP是GPM的结构性选择;审计时不可忽视 运维侧滥用或被入侵账户利用bin/gpm 的场景。

#

3.6-推导最大危害

在 已具备管理员账号 的前提下,可以构造 结构符合Grav插件约定的恶意包(含blueprints.yaml、主类PHP等),在onPluginsInitialized等周期钩子里写WebShell、读配置中的数据库口令、或横向改写站点文件

这与后台本来就能改模板不同:任意PHP在服务器进程身份下运行,可调用system、proc_open、读/etc/passwd(在权限允许时)等,属于全站失陷。若攻击者通过CSRF、会话固定或stolen cookie 借用管理员浏览器,也能让用户无感知下完成上传,放大社工与浏览器侧风险。(也同下面的漏洞)

3.7-Login插件问题

根本原因

register()方法中,groups和access字段仅在输入数据中不存在时,才会被设置为配置默认值:

// Line 254-260if&nbsp;(!isset($data['groups'])) {&nbsp; &nbsp;&nbsp;$groups&nbsp;= (array)&nbsp;$this->config->get('plugins.login.user_registration.groups', []);&nbsp; &nbsp;&nbsp;if&nbsp;(count($groups) >&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$data['groups'] =&nbsp;$groups;&nbsp; &nbsp; }}// Line 262-267if&nbsp;(!isset($data['access'])) {&nbsp; &nbsp;&nbsp;$access&nbsp;= (array)&nbsp;$this->config->get('plugins.login.user_registration.access.site', []);&nbsp; &nbsp;&nbsp;if&nbsp;(count($access) >&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$data['access']['site'] =&nbsp;$access;&nbsp; &nbsp; }}

如果攻击者在POST请求体中包含了groups或access!isset()检查将返回false,从而跳过配置默认值的设置,攻击者提供的值将原封不动地传递下去。随后,这些值会被直接赋给用户对象:

if&nbsp;(isset($data['groups'])) {&nbsp; &nbsp;&nbsp;$user->groups =&nbsp;$data['groups']; &nbsp;// attacker-controlled}if&nbsp;(isset($data['access'])) {&nbsp; &nbsp;&nbsp;$user->access =&nbsp;$data['access']; &nbsp;// attacker-controlled}$user->save();

validateField()方法包含一个switch语句,仅对以下字段进行验证:username、password、password2、permissions、statelanguagegroups和access字段会落入default分支,完全不受任何验证。

# user/config/plugins/login.yamluser_registration:&nbsp; enabled:&nbsp;true&nbsp; fields:&nbsp; &nbsp; - username&nbsp; &nbsp; - password&nbsp; &nbsp; - email&nbsp; &nbsp; - fullname&nbsp; &nbsp; -&nbsp;groups&nbsp;&nbsp;# ← 启用攻击&nbsp; &nbsp; - access &nbsp;# ← 启用攻击

3.8-攻击链路

未认证攻击者&nbsp; │&nbsp; ├─[1] 发送恶意注册请求 (CVE-2026-42613)&nbsp; │ &nbsp; &nbsp; POST /user_register&nbsp; │ &nbsp; &nbsp; 注入 data[groups][]=admins & data[access][admin][super]=true&nbsp; │ &nbsp; &nbsp; └─ Login 插件 register() 方法未校验&nbsp;groups/access → 写入用户 YAML&nbsp; │&nbsp; ├─[2] 获得 admin.super 超级管理员账户&nbsp; │ &nbsp; &nbsp; (如 attacker / Str0ngP@ss!)&nbsp; │&nbsp; ├─[3] 登录管理后台 /admin&nbsp; │ &nbsp; &nbsp; 获取 login-nonce → 提交凭证 → 建立会话&nbsp; │&nbsp; ├─[4] 上传恶意插件 ZIP (CVE-2026-42607)&nbsp; │ &nbsp; &nbsp; POST /admin/tools/direct-install?task=directInstall&nbsp; │ &nbsp; &nbsp; └─ AdminController::taskDirectInstall() 接收文件 → 调用 Installer::install()&nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp;└─ Installer::unZip() 使用 ZipArchive::extractTo() 直接解压(无文件类型/路径检查)&nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp;└─ 插件文件落地至 user/plugins/shellplugin/&nbsp; │&nbsp; ├─[5] 插件初始化触发&nbsp; │ &nbsp; &nbsp; Grav 加载插件 → 执行 shellplugin.php 中的 onPluginsInitialized()&nbsp; │ &nbsp; &nbsp; └─ 恶意代码在插件目录写入 Webshell(如 shell.php)&nbsp; │&nbsp; └─[6] 远程命令执行&nbsp; &nbsp; &nbsp; &nbsp; 访问 /user/plugins/shellplugin/shell.php?cmd=whoami&nbsp; &nbsp; &nbsp; &nbsp; → 系统命令执行,服务器完全控制

#


0x4 修复建议

1、升级最新版本:将Grav升级至2.0.0-beta.2及以上版本

https://github.com/getgrav/grav

2、临时防护措施:

  • 限制访问:对 /admin/tools/direct-install 做IP白名单或额外HTTP认证,仅维护窗口开放
  • 审计监控:部署文件完整性监控,监控user/plugins/**与Web根新建.php(或者全站)
  • 运维策略:对更新包只做 签名校验后的安装。
  • 防火墙 / WAF:拦截或告警POST+URI含direct-install + task=directInstall + multipart的组合;对uploaded_file带.zip的上传加强审计与速率限制

#


免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

/** 别说,吃着雪糕敲文章还挺舒服**/

#


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:404号浪漫 404号浪漫 404号浪漫《Grav CMS 组合拳漏洞| CVE-2026-42613&CVE-2026-42607复现&研究》

评论:0   参与:  0