GitHub Security Advisory: GHSA-vj3m-2g9h-vm4p
Package: getgrav/grav (COMPOSER)
Severity: CRITICAL (CVSS 0.0)
CWE: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection'), CWE-502: Deserialization of Untrusted Data
Vulnerable versions: < 2.0.0-beta.2
Patched in: 2.0.0-beta.2

Summary: Grav has multiple RCE vectors: unsafe unserialize (x3), command injection in git clone, SSTI blocklist bypass

Details:
Multiple RCE vectors were found in Grav CMS. Three are critical, two are high.

**1. Unsafe unserialize() in JobQueue — direct RCE gadget (Critical)**

`system/src/Grav/Common/Scheduler/JobQueue.php:465` calls `unserialize(base64_decode(...))` without restricting `allowed_classes`. The `Job` class has `call_user_func_array($this->command, $this->args)` in its execution path, which is a direct gadget chain — inject a serialized `Job` with `command = 'system'` and `args = ['whoami']`.

The same codebase actually has a `Serializable` trait that correctly restricts classes, so this inconsistency stands out.

**2. Unsafe unserialize() in FileCache — arbitrary class instantiation (Critical)**

`system/src/Grav/Framework/Cache/Adapter/FileCache.php:75` does `unserialize($value, ['allowed_classes' => true])`. That `true` allows instantiation of any class. If an attacker can write to the cache directory (via any file write primitive), they get object injection → RCE.

**3. Unsafe unserialize() in Session (High)**

`system/src/Grav/Common/Session.php:116` — same `allowed_classes => true` pattern on session data. Lower severity since session storage is typically more restricted.

**4. Command injection in git clone (Critical)**

`system/src/Grav/Console/Cli/InstallCommand.php:150` — only `$this->destination` uses `escapeshellarg()`. The `$data['branch']`, `$data['url']`, and `$data['path']` variables go directly into the shell command without escaping. Admin-accessible via plugin/theme installation.

**5. SSTI blocklist bypass (High)**

`system/src/Grav/Common/Security.php:267-286` — `cleanDangerousTwig()` blocks `twig_array_map` and `twig_array_filter` but not `twig_array_reduce`. Also missing `file_get_contents` and `fwrite` from the dangerous function blocklist. An attacker who can inject Twig templates can bypass the security filter.

All five are independently exploitable. The unserialize issues are the most concerning since they don't require admin access if there's any file write primitive.

— ProScan AppSec | proscan.one


---

## Maintainer note — fix applied (2026-04-24)

Fixed in Grav core on the `2.0` branch: commit [`c66dfeb5f`](https://github.com/getgrav/grav/commit/c66dfeb5f) (items #1, #2, #3, #4) and commit [`38685ac25`](https://github.com/getgrav/grav/commit/38685ac25) + [`c66dfeb5f`](https://github.com/getgrav/grav/commit/c66dfeb5f) (item #5) — ships in **2.0.0-beta.2**.

All five vectors addressed:

1. **Scheduler\JobQueue unsafe unserialize** — `serialized_job` now carries a sibling `serialized_job_hmac` signed with `Security::getNonceKey()`. `reconstructJob` refuses to unserialize an item whose HMAC is missing/mismatched and falls through to the safe structured-fields rebuild. A tampered queue file can no longer smuggle a forged `Job` for direct RCE via `Job::exec → call_user_func_array`.  
   → [`system/src/Grav/Common/Scheduler/JobQueue.php`](https://github.com/getgrav/grav/blob/2.0/system/src/Grav/Common/Scheduler/JobQueue.php)

2.