CVE-2026-65608
Grav before 2.0.9 Remote Code Execution via FlexDirectory
Grav versions >= 1.7.0 and before 2.0.9 contain a remote code execution vulnerability. FlexDirectory::dynamicDataField() resolves blueprint data-*@: directives by calling call_user_func_array() on attacker-influenced input, validating only that the target is callable (is_callable()) without restricting dangerous functions such as exec, system, passthru, or shell_exec. Because FlexDirectory registers this handler for every Flex directory, it bypasses the validation added to Blueprint::dynamicData() in 2.0.7 (GHSA-fj2p-qj2f-74v5). Any authenticated user with create or update permission on any Flex-based directory (Flex Users, Flex Pages, Flex Objects, or custom Flex types) can execute arbitrary shell commands on the server.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.7
Grav before 2.0.9 Remote Code Execution via FlexDirectory
Grav versions >= 1.7.0 and before 2.0.9 contain a remote code execution vulnerability. FlexDirectory::dynamicDataField() resolves blueprint data-*@: directives by calling call_user_func_array() on attacker-influenced input, validating only that the target is callable (is_callable()) without restricting dangerous functions such as exec, system, passthru, or shell_exec. Because FlexDirectory registers this handler for every Flex directory, it bypasses the validation added to Blueprint::dynamicData() in 2.0.7 (GHSA-fj2p-qj2f-74v5). Any authenticated user with create or update permission on any Flex-based directory (Flex Users, Flex Pages, Flex Objects, or custom Flex types) can execute arbitrary shell commands on the server.
- Record
CVE-2026-65608- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470
- Updated
- 2026-11-02
EUVDEUVD-2026-48244
8.7
Grav versions >= 1.7.0 and before 2.0.9 contain a remote code execution vulnerability. FlexDirectory::dynamicDataField() resolves blueprint data-*@: directives by calling call_user_func_array() on attacker-influenced input, validating only that the target is callable (is_callable()) without restricting dangerous functions such as exec, system, passthru, or shell_exec. Because FlexDirectory registers this handler for every Flex directory, it bypasses the validation added to Blueprint::dynamicData() in 2.0.7 (GHSA-fj2p-qj2f-74v5). Any authenticated user with create or update permission on any Flex-based directory (Flex Users, Flex Pages, Flex Objects, or custom Flex types) can execute arbitrary shell commands on the server.
- Record
EUVD-2026-48244- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-c4wf-2xxc-68qm
8.7
Grav: FlexDirectory::dynamicDataField() executes arbitrary callables from blueprint data with no validation
Summary
A missing validation check in Grav's Flex framework lets an account holding nothing but an ordinary object-create permission on a single Flex directory execute arbitrary shell commands on the server. Any authenticated user with create or update rights on a Flex-based directory (Flex Users, Flex Pages, Flex Objects, or any custom Flex type) can trigger it the moment a blueprint field anywhere in that directory carries a data-*@: directive, since the code that resolves those directives calls call_user_func_array() on attacker-influenced input with no restriction at all.
This is a bypass of GHSA-fj2p-qj2f-74v5, already patched in 2.0.7. That fix added real validation to Blueprint::dynamicData(), but Grav's Flex system routes the same directive through a separate, unprotected method, FlexDirectory::dynamicDataField(), which never received the same fix.
Details
Grav blueprints support action-property@: directives, YAML keys that tell the blueprint engine to compute a field's value dynamically by calling a function. Blueprint::init() (system/src/Grav/Common/Data/Blueprint.php:167-177) resolves these by checking for a registered handler first, and only falling back to the built-in dynamic{Action} method if none is registered:
foreach ($data as $property => $call) {
$action = $call['action'];
$method = 'dynamic' . ucfirst((string) $action);
$call['object'] = $this->object;
if (isset($this->handlers[$action])) {
$callable = $this->handlers[$action];
$callable($current, $property, $call);
} elseif (method_exists($this, $method)) {
$this->{$method}($current, $property, $call);
}
}
FlexDirectory::getBlueprint() (system/src/Grav/Framework/Flex/FlexDirectory.php:878-880) registers exactly such a handler for the data action, for every Flex directory:
$blueprint->addDynamicHandler('data', function (array &$field, $property, array &$call) {
$this->dynamicDataField($field, $property, $call);
});
Because a handler is registered, Blueprint::init() never falls through to the patched Blueprint::dynamicData(). It calls FlexDirectory::dynamicDataField() instead (system/src/Grav/Framework/Flex/FlexDirectory.php:906-928):
protected function dynamicDataField(array &$field, $property, array $call)
{
$params = $call['params'];
if (is_array($params)) {
$function = array_shift($params);
} else {
$function = $params;
$params = [];
}
$object = $call['object'];
if ($function === '\Grav\Common\Page\Pages::pageTypes') {
$params = [$object instanceof PageInterface && $object->isModule() ? 'modular' : 'standard'];
}
$data = null;
if (is_callable($function)) {
$data = call_user_func_array($function, $params);
}
// ...
}
is_callable() only checks that $function resolves to something callable. It does not check whether calling it is safe. 'exec', 'system', 'passthru', and 'shell_exec' are all valid PHP callables, so this passes them through without complaint.
Compare this to the patched Blueprint::dynamicData() (system/src/Grav/Common/Data/Blueprint.php:426-448), which calls $this->isSafeDynamicCall($function, $params) before doing anything. That method denies known command-execution functions (exec, system, passthru, shell_exec, popen, proc_open, pcntl_exec), known code-execution functions (assert, preg_replace, create_function, include, require), and recursively checks the argument list for a dangerous callable smuggled in as a parameter, which is the trampoline pattern the original GHSA exploited through Utils::arrayFilterRecursive. None of that logic exists in dynamicDataField().
Version tested: current master, commit fae9e1bf2c40ce0b50d0dfce647aaa1d22f98969. git describe reports this as 2.0.8-2-gfae9e1bf2, two commits past the 2.0.8 tag. I checked those two commits…
- Record
GHSA-c4wf-2xxc-68qm- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-pp9r-ppc4-25w4
8.7
Duplicate Advisory: Grav: FlexDirectory::dynamicDataField() executes arbitrary callables from blueprint data with no validation
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-c4wf-2xxc-68qm. This link is maintained to preserve external references.
Original Description
Grav versions >= 1.7.0 and before 2.0.9 contain a remote code execution vulnerability. FlexDirectory::dynamicDataField() resolves blueprint data-*@: directives by calling call_user_func_array() on attacker-influenced input, validating only that the target is callable (is_callable()) without restricting dangerous functions such as exec, system, passthru, or shell_exec. Because FlexDirectory registers this handler for every Flex directory, it bypasses the validation added to Blueprint::dynamicData() in 2.0.7 (GHSA-fj2p-qj2f-74v5). Any authenticated user with create or update permission on any Flex-based directory (Flex Users, Flex Pages, Flex Objects, or custom Flex types) can execute arbitrary shell commands on the server.
- Record
GHSA-pp9r-ppc4-25w4- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470
- Updated
- 2026-11-02
NVD
8.7
Grav versions >= 1.7.0 and before 2.0.9 contain a remote code execution vulnerability. FlexDirectory::dynamicDataField() resolves blueprint data-*@: directives by calling call_user_func_array() on attacker-influenced input, validating only that the target is callable (is_callable()) without restricting dangerous functions such as exec, system, passthru, or shell_exec. Because FlexDirectory registers this handler for every Flex directory, it bypasses the validation added to Blueprint::dynamicData() in 2.0.7 (GHSA-fj2p-qj2f-74v5). Any authenticated user with create or update permission on any Flex-based directory (Flex Users, Flex Pages, Flex Objects, or custom Flex types) can execute arbitrary shell commands on the server.
- Record
CVE-2026-65608- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470
- Updated
- 2026-11-02
OSV
8.7
Grav before 2.0.9 Remote Code Execution via FlexDirectory
Grav versions >= 1.7.0 and before 2.0.9 contain a remote code execution vulnerability. FlexDirectory::dynamicDataField() resolves blueprint data-*@: directives by calling call_user_func_array() on attacker-influenced input, validating only that the target is callable (is_callable()) without restricting dangerous functions such as exec, system, passthru, or shell_exec. Because FlexDirectory registers this handler for every Flex directory, it bypasses the validation added to Blueprint::dynamicData() in 2.0.7 (GHSA-fj2p-qj2f-74v5). Any authenticated user with create or update permission on any Flex-based directory (Flex Users, Flex Pages, Flex Objects, or custom Flex types) can execute arbitrary shell commands on the server.
- Record
CVE-2026-65608- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-470
- Updated
- 2026-11-02
OSVGHSA-c4wf-2xxc-68qm
8.7
Grav: FlexDirectory::dynamicDataField() executes arbitrary callables from blueprint data with no validation
Summary
A missing validation check in Grav's Flex framework lets an account holding nothing but an ordinary object-create permission on a single Flex directory execute arbitrary shell commands on the server. Any authenticated user with create or update rights on a Flex-based directory (Flex Users, Flex Pages, Flex Objects, or any custom Flex type) can trigger it the moment a blueprint field anywhere in that directory carries a data-*@: directive, since the code that resolves those directives calls call_user_func_array() on attacker-influenced input with no restriction at all.
This is a bypass of GHSA-fj2p-qj2f-74v5, already patched in 2.0.7. That fix added real validation to Blueprint::dynamicData(), but Grav's Flex system routes the same directive through a separate, unprotected method, FlexDirectory::dynamicDataField(), which never received the same fix.
Details
Grav blueprints support action-property@: directives, YAML keys that tell the blueprint engine to compute a field's value dynamically by calling a function. Blueprint::init() (system/src/Grav/Common/Data/Blueprint.php:167-177) resolves these by checking for a registered handler first, and only falling back to the built-in dynamic{Action} method if none is registered:
foreach ($data as $property => $call) {
$action = $call['action'];
$method = 'dynamic' . ucfirst((string) $action);
$call['object'] = $this->object;
if (isset($this->handlers[$action])) {
$callable = $this->handlers[$action];
$callable($current, $property, $call);
} elseif (method_exists($this, $method)) {
$this->{$method}($current, $property, $call);
}
}
FlexDirectory::getBlueprint() (system/src/Grav/Framework/Flex/FlexDirectory.php:878-880) registers exactly such a handler for the data action, for every Flex directory:
$blueprint->addDynamicHandler('data', function (array &$field, $property, array &$call) {
$this->dynamicDataField($field, $property, $call);
});
Because a handler is registered, Blueprint::init() never falls through to the patched Blueprint::dynamicData(). It calls FlexDirectory::dynamicDataField() instead (system/src/Grav/Framework/Flex/FlexDirectory.php:906-928):
protected function dynamicDataField(array &$field, $property, array $call)
{
$params = $call['params'];
if (is_array($params)) {
$function = array_shift($params);
} else {
$function = $params;
$params = [];
}
$object = $call['object'];
if ($function === '\Grav\Common\Page\Pages::pageTypes') {
$params = [$object instanceof PageInterface && $object->isModule() ? 'modular' : 'standard'];
}
$data = null;
if (is_callable($function)) {
$data = call_user_func_array($function, $params);
}
// ...
}
is_callable() only checks that $function resolves to something callable. It does not check whether calling it is safe. 'exec', 'system', 'passthru', and 'shell_exec' are all valid PHP callables, so this passes them through without complaint.
Compare this to the patched Blueprint::dynamicData() (system/src/Grav/Common/Data/Blueprint.php:426-448), which calls $this->isSafeDynamicCall($function, $params) before doing anything. That method denies known command-execution functions (exec, system, passthru, shell_exec, popen, proc_open, pcntl_exec), known code-execution functions (assert, preg_replace, create_function, include, require), and recursively checks the argument list for a dangerous callable smuggled in as a parameter, which is the trampoline pattern the original GHSA exploited through Utils::arrayFilterRecursive. None of that logic exists in dynamicDataField().
Version tested: current master, commit fae9e1bf2c40ce0b50d0dfce647aaa1d22f98969. git describe reports this as 2.0.8-2-gfae9e1bf2, two commits past the 2.0.8 tag. I checked those two commits…
- Record
GHSA-c4wf-2xxc-68qm- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470
- Updated
- 2026-11-02
OSVGHSA-pp9r-ppc4-25w4
8.7
Duplicate Advisory: Grav: FlexDirectory::dynamicDataField() executes arbitrary callables from blueprint data with no validation
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-c4wf-2xxc-68qm. This link is maintained to preserve external references.
Original Description
Grav versions >= 1.7.0 and before 2.0.9 contain a remote code execution vulnerability. FlexDirectory::dynamicDataField() resolves blueprint data-*@: directives by calling call_user_func_array() on attacker-influenced input, validating only that the target is callable (is_callable()) without restricting dangerous functions such as exec, system, passthru, or shell_exec. Because FlexDirectory registers this handler for every Flex directory, it bypasses the validation added to Blueprint::dynamicData() in 2.0.7 (GHSA-fj2p-qj2f-74v5). Any authenticated user with create or update permission on any Flex-based directory (Flex Users, Flex Pages, Flex Objects, or custom Flex types) can execute arbitrary shell commands on the server.
- Record
GHSA-pp9r-ppc4-25w4- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
getgrav/gravPackagist |
1.7.0 up to 2.0.9 | 2.0.9 |
getgrav/gravPackagist |
1.7.0 up to 2.0.9, plus listed versions 1.7.11.7.101.7.121.7.131.7.14 Show 45 moreShow less1.7.151.7.161.7.171.7.181.7.191.7.201.7.211.7.221.7.231.7.241.7.251.7.261.7.26.11.7.271.7.27.11.7.281.7.291.7.29.11.7.31.7.301.7.311.7.321.7.331.7.341.7.351.7.361.7.371.7.37.11.7.381.7.391.7.39.11.7.39.21.7.39.31.7.39.41.7.41.7.401.7.411.7.41.11.7.41.21.7.421.7.42.11.7.42.21.7.42.31.7.431.7.44 |
2.0.9 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-65608 is a high vulnerability (CVSS 8.7) in Getgrav Grav, published July 23, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 1.12%, the 66th percentile. Fixed in getgrav/grav 2.0.9.
References
github.com/getgrav/grav/security/advisories/GHSA-c4wf-2xxc-68qm
github.com/getgrav/grav/commit/fae9e1bf2c40ce0b50d0dfce647aaa1d22f98969
vulncheck.com/advisories/grav-before-remote-code-execution-via-flexdirectory
nvd.nist.gov/vuln/detail/CVE-2026-65608
github.com/advisories/GHSA-c4wf-2xxc-68qm
github.com/advisories/GHSA-pp9r-ppc4-25w4
github.com/CVEProject/cvelistV5/tree/main/cves/2026/65xxx/CVE-2026-65608.json
github.com/getgrav/grav
Left out here: 73 versions and the rest of 2 source descriptions. The JSON record has them.