Krytyczna

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

  • Ujawniono: 17.09.2026
  • Aktualizacja: 11.10.2026 10:22

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:

bash
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.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka Composer getgrav/grav < 2.0.15 2.0.15

Odnośniki

Ocena CVSS 8.8/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Słabość (CWE)
CWE-94
Platforma
Composer
Paczka
getgrav/grav
Ujawniono
17.09.2026
Aktualizacja
11.10.2026 10:22

Źródła i licencje

OSV / GitHub Advisory Database - GHSA-f8wv-xp27-6gq7 - CC BY 4.0

Historia podatności: getgrav/grav

Wszystkie opisane podatności tej paczki (Paczka Composer), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (99) w archiwum
Wszystkich wpisów
99
Pierwsza → ostatnia
11.2019 09.2026
Bez poprawki
6
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 7
  • Wysoka 47
  • Średnia 41
  • Niska 4

Ujawnienia w latach

  1. 2019: 1
  2. 2020: 1
  3. 2021: 4
  4. 2022: 10
  5. 2023: 5
  6. 2024: 8
  7. 2025: 23
  8. 2026: 47
Istotność:
  1. Wysoka · 8.8 CVE-2026-72819 < 2.0.13 2.0.13
  2. Grav: Blueprint dynamic-data bare-function branch is denylist-gated and omits error_log, giving arbitrary file write ten wpis
    Krytyczna · 8.8 CVE-2026-75827 < 2.0.15 2.0.15
  3. Wysoka CVE-2026-53653 >= 2.0.0-beta.1, < 2.0.0-rc.8, < 1.7.53 2.0.0-rc.8, 1.7.53
  4. Wysoka · 8.9 CVE-2026-42611 < 2.0.0-beta.2 2.0.0-beta.2
  5. Wysoka · 8.5 CVE-2026-42612 < 2.0.0-beta.2 2.0.0-beta.2
  6. Krytyczna · 9.4 CVE-2026-42613 < 2.0.0-beta.2 2.0.0-beta.2
  7. Krytyczna · 9.1 CVE-2025-66844 <= 1.7.49.5 brak poprawki
  8. Wysoka CVE-2025-66298 < 1.8.0-beta.27 1.8.0-beta.27
  9. Wysoka CVE-2025-66294 < 1.8.0-beta.27 1.8.0-beta.27
  10. Wysoka · 8.5 CVE-2025-66300 < 1.8.0-beta.27 1.8.0-beta.27
  11. Wysoka · 8.8 CVE-2025-66299 < 1.8.0-beta.27 1.8.0-beta.27
  12. Wysoka · 8.5 CVE-2024-34082 < 1.7.46 1.7.46
  13. Wysoka · 8.8 CVE-2024-28119 < 1.7.45 1.7.45
  14. Wysoka · 8.8 CVE-2024-28118 < 1.7.45 1.7.45
  15. Wysoka · 8.8 CVE-2024-28117 < 1.7.45 1.7.45
  16. Wysoka · 8.8 CVE-2024-27921 < 1.7.45 1.7.45
  17. Wysoka · 8.8 CVE-2024-27923 < 1.7.43 1.7.43
  18. Wysoka · 7.2 CVE-2023-37897 < 1.7.42.2 1.7.42.2
  19. Wysoka · 7.2 CVE-2023-34448 < 1.7.42 1.7.42
  20. Wysoka · 7.2 CVE-2023-34252 < 1.7.42 1.7.42
  21. Krytyczna · 9.1 CVE-2023-34251 < 1.7.42 1.7.42
  22. Wysoka · 7.2 CVE-2022-2073 < 1.7.34 1.7.34
  23. Wysoka · 8.1 CVE-2020-29555 >= 1.7.0-beta.1, <= 1.7.0-rc.17, < 1.6.30 1.6.30
  24. Wysoka · 8.8 CVE-2020-29553 >= 1.7.0-beta.1, <= 1.7.0-rc.17, < 1.6.30 1.6.30
  25. Wysoka · 7.1 CVE-2022-0970 < 1.7.31 1.7.31
  26. Wysoka · 8.8 CVE-2021-3924 <= 1.7.24 brak poprawki
  27. Wysoka · 8.4 CVE-2021-29440 < 1.7.11 1.7.11

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.