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

Grav CMS had five RCE vectors: three unsafe unserialize() calls (JobQueue, FileCache, Session), unescaped git clone args, and an SSTI blocklist bypass via twig_array_reduce.

- Payout: Research
- Severity: critical
- Program: Grav
- Found by: Proscan-one
- Disclosed: 2026-05-05
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-vj3m-2g9h-vm4p
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-vj3m-2g9h-vm4p
- Tags: area:web domain:web impact:rce kind:research platform:ghsa sev:critical tech:php technique:incomplete-fix technique:source-review vuln:command-injection vuln:deserialization vuln:rce vuln:ssti

**Target:** Grav CMS PHP application (JobQueue, FileCache, Session, InstallCommand, Twig security filter)
**Root cause:** unserialize() called without allowed_classes restriction (or with allowed_classes=>true) on attacker-influenceable data, plus unescaped shell args in git clone and an incomplete Twig dangerous-function blocklist.
**Steps:**
1. JobQueue: inject serialized Job with `command = 'system'` and `args = ['whoami']` into base64-encoded queue data at JobQueue.php:465; Job::exec calls `call_user_func_array($this->command, $this->args)`.
2. FileCache: write to cache directory, then FileCache.php:75 `unserialize($value, ['allowed_classes' => true])` instantiates arbitrary classes → object injection.
3. Session: same `allowed_classes => true` pattern on session data at Session.php:116.
4. InstallCommand: supply `branch`, `url`, or `path` in `user/.dependencies`; only `destination` is escaped, so values reach the shell unescaped.
5. SSTI: inject Twig using `twig_array_reduce` (not blocked) or `file_get_contents`/`fwrite` to bypass cleanDangerousTwig().
**How found:** Source review: noticed JobQueue unserialize lacked allowed_classes while a Serializable trait elsewhere correctly restricted classes; spotted `allowed_classes => true` in FileCache/Session; found only destination escaped in InstallCommand; compared cleanDangerousTwig blocklist against Twig callable functions and found twig_array_reduce missing.
**Exploitation:** Unserialize vectors require a file-write primitive (or admin access for git clone) to plant a malicious serialized payload; the Job gadget yields direct command execution via call_user_func_array. Git clone injection is admin-accessible via plugin/theme installation.
**Escalation:** Unserialize issues escalate from any file-write primitive to RCE without admin access.
**Insight:** When a codebase has a correct safe pattern (Serializable trait restricting classes), grep for every unserialize() that deviates from it — inconsistency is the tell. For blocklists, enumerate the full Twig callable function set and diff against the blocklist rather than testing known-bad names.

