Sep 18, 2026·4 min read·46 visits
Incomplete denylist validation of bare PHP functions in Grav Blueprints allows authenticated users to trigger arbitrary file writes and achieve remote code execution.
An arbitrary file write and remote code execution vulnerability exists in Grav CMS before version 2.0.15. The vulnerability is caused by using an incomplete denylist validation approach for bare PHP functions in the Blueprint dynamic-data compiler, allowing authenticated users with page-editing or blueprint-configuration privileges to execute arbitrary functions such as error_log.
Grav CMS relies on YAML configurations called Blueprints to dynamically define administration forms, page configurations, and data structures. To populate these structures with dynamic options, Blueprints use dynamic data directives indicated by an '@' suffix, such as 'data-options@'. These directives instruct the compiler to execute a designated callable using PHP's call_user_func_array function.\n\nBecause Blueprints can be altered by users with administrative or page-editing privileges, executing arbitrary PHP callables poses a direct security risk. Grav implements the Blueprint::isSafeDynamicCall method to validate and authorize dynamic callables before execution. In versions prior to 2.0.15, this safety mechanism was insufficient, allowing arbitrary function execution through unprotected call paths.
The primary defect resides in system/src/Grav/Common/Data/Blueprint.php within the isSafeDynamicCall method. While class method callables (e.g., Class::method) were constrained by a strict positive allowlist, bare string functions (e.g., 'system' or 'error_log') were checked against an incomplete denylist in Utils::isDangerousFunction.\n\nDenylists are architecturally prone to omission errors. In this implementation, dangerous built-in PHP functions like 'error_log' and 'stream_socket_client' were not registered within the denylist. Consequently, when an input specified 'error_log' as the dynamic function, the function returned true, designating the call as safe. This allowed unauthorized users to execute unrestricted administrative functions via the template processor.
Prior to version 2.0.15, the bare-function validation in system/src/Grav/Common/Data/Blueprint.php relied on checking whether a string function was registered in a list of dangerous functions:\n\nphp\n// Vulnerable Implementation\nif (is_string($function) && Utils::isDangerousFunction($function)) {\n return false;\n}\nreturn true;\n\n\nTo resolve this vulnerability in version 2.0.15, the developers removed the denylist model entirely and implemented a symmetrical allowlist-based verification. Bare functions must now be explicitly declared in the static allowlist array or registered by trusted plugins:\n\nphp\n// Patched Implementation (Commit d5f89d95d94cc155bc3be998ee85527a7be073dd)\nif (!is_string($function) || !isset(self::$allowedDynamicCallables[strtolower(ltrim($function, '\\'))])) {\n return false;\n}\n\n\nAdditionally, the patch introduced provenance-based trust. Blueprints compiled directly from local filesystem template files are marked as trusted and bypass certain allowlist restrictions. Blueprints derived from user-controlled page frontmatter are classified as untrusted, enforcing strict allowlist checks to block arbitrary call execution.
An attacker with page-editing or blueprint-configuration permissions can exploit this behavior by writing a crafted YAML payload into a page configuration or frontmatter section. The payload defines a dynamic directive pointing to 'error_log' and supplies arguments to trigger a file write. Because 'error_log' accepts a message, a message type, and a destination file path, it acts as an arbitrary file write primitive.\n\nyaml\nform:\n fields:\n exploit_field:\n type: text\n data-options@: ['error_log', ['<?php system($_GET["cmd"]); ?>', 3, 'user/pages/shell.php']]\n\n\nUpon receiving a request that triggers blueprint compilation, Grav parses the field and invokes error_log. The function creates the target PHP file 'user/pages/shell.php' inside a web-accessible directory. The attacker can then request this file directly to execute arbitrary operating system commands in the security context of the web server daemon.\n\nmermaid\ngraph LR\n A["Attacker Modifies Blueprint"] --> B["Grav Parses Blueprint Directive"]\n B --> C["isSafeDynamicCall Checks Bare Function"]\n C -->|"Bypasses Denylist"| D["call_user_func_array Executes error_log"]\n D --> E["PHP Payload Written to Disk"]\n
The vulnerability carries a CVSS base score of 8.8 (High) under CVSS v3.1, and 9.3 (Critical) under CVSS v4.0. Successful exploitation allows an authenticated user with low-level administrative or page-editing privileges to completely bypass intended access restrictions and execute arbitrary code on the hosting server.\n\nThis impact compromises host confidentiality, integrity, and availability. An attacker can read database credentials, exfiltrate sensitive application data, alter page content, or pivot laterally to other internal systems. This risk is particularly high in multi-tenant environments where page editors should not have server-level administrative access.
Remediation requires upgrading Grav Core to version 2.0.15 or higher. This release integrates the provenance-based trust engine and enforces the strict allowlist check for all dynamic callables.\n\nIf an immediate upgrade is not feasible, administrators should audit the filesystem for unauthorized dynamic directives. Inspect existing page frontmatter files for '@' characters combined with PHP system calls or raw string functions. Additionally, deploy web application firewall rules to block requests containing '@' suffixes in fields coupled with raw PHP function names.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Grav CMS Core getgrav | < 2.0.15 | 2.0.15 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-94 |
| Attack Vector | Network |
| CVSS v3.1 | 8.8 (High) |
| EPSS Score | 0.00777 |
| Exploit Status | PoC Verified |
| KEV Status | Not Listed |
The software constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes the input before it is executed.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.