GHSA-w48r-jppp-rcfw: Grav Vulnerable to Remote Code Execution (RCE) via Malicious Plugin ZIP Upload in Direct Install Feature
SummaryAn authenticated user with administrative privileges can achieve Remote Code Execution (RCE) by uploading a specially crafted ZIP file through the "Direct Install" tool. While the system attempts to block direct .php file uploads, it fails to inspect the contents of uploaded ZIP archives. Once a malicious plugin is extracted, it can execute arbitrary PHP code or drop a persistent web shell on the server.DetailsThe vulnerability exists in the handling of the directInstall task within the Admin plugin and the Grav Package Manager (GPM) core.Vulnerable Endpoints: /admin/tools/direct-installVulnerable Logic: AdminController.php (lines 1247-1295) and Gpm.php (lines 214-285).Root Cause: The function Installer::install() (called in Gpm.php:291) extracts the contents of the ZIP file directly into the /user/plugins/ or /user/themes/ directories without validating the file extensions or the content of the files inside the archive.PoCPrepare the Malicious PluginCreate a directory named shellplugin and add the following files:shellplugin.php:(Also include a basic blueprints.yaml and shellplugin.yaml as per Grav standards).Create the ZIP Archive1. Login and Bypass Rate Limit via X-Forwarded-For2. Extract Admin Nonce from Tools Page3. Upload and Execute4. VerificationAccess the dropped shell to confirm command execution:
`curl -s "http://127.0.0.1/shell.php?cmd=whoami"`ImpactVulnerability Type: Remote Code Execution (RCE) / Path Traversal (via extraction).Who is impacted: Any Grav installation where the Admin plugin is enabled and an attacker has gained administrative access (or an administrator is tricked into uploading a malicious ZIP).Severity: Critical. Although it requires admin privileges, the ability to gain full server control (system-level access) makes this a high-impact finding, especially in multi-user environments or via CSRF/Session hijacking.Maintainer note — partial fix applied (2026-04-24)Fixed in Grav core on the `2.0` branch: commit [`5a12f9be8`](https://github.com/getgrav/grav/commit/5a12f9be8) — ships in **2.0.0-beta.2**.**What changed (path layer):** `Installer::unZip` now pre-validates every entry name before calling `ZipArchive::extractTo`, and aborts the install if any entry looks like a Zip Slip primitive — `..` path segments, absolute paths (Unix `/…` or Windows `C:\…`/`\…`), or NUL bytes. A crafted ZIP can no longer write files outside the target `user/plugins/<slug>` or `user/themes/<slug>` directory.**Explicit scope limitation:** the "well-formed but malicious plugin code" angle of the PoC — uploading a plugin whose own PHP is the payload — is **not** addressed by this change. `directInstall` is an administrator-only operation whose explicit purpose is to install arbitrary PHP; defending against it would require a plugin-signing or marketplace-allowlist feature, which is a separate roadmap item. Administrators should only install plugins from trusted sources. This is now explicitly documented in the commit note.**Files:**[`system/src/Grav/Common/GPM/Installer.php`](https://github.com/getgrav/grav/blob/2.0/system/src/Grav/Common/GPM/Installer.php) — new `isSafeArchiveEntry()` helper + pre-extract validation loop.[`tests/unit/Grav/Common/Security/ZipSlipSecurityTest.php`](https://github.com/getgrav/grav/blob/2.0/tests/unit/Grav/Common/Security/ZipSlipSecurityTest.php) — 21 cases covering Unix/Windows/URL-encoded traversal primitives and legitimate plugin names.AcknowledgementsThe issue was identified by Security Researcher **Mustafa Murat Akgül**.
CVSS v3.1: 9.1