Every CVE Search/ Get API key

CVE-2026-65608

Grav before 2.0.9 Remote Code Execution via FlexDirectory

8.7 HighFix available

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

1.12%Up 0.3 points since Jul 24, 2026
90%50%10%1%

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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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

Affected packages
PackageAffectedFixed
getgrav/grav
Packagist
1.7.0 up to 2.0.9
2.0.9
getgrav/grav
Packagist
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 less
1.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

History
RecordedChangedSources
affected
first indexedCVE 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

This page doesn't list everything

Left out here: 73 versions and the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open