
Prueba de concepto y laboratorio para CVE-2026-75827, una escritura arbitraria de archivos en Grav mediante error_log de datos dinámicos de Blueprint, con script de reproducción y laboratorio Docker.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · CVE-2026-75827
grav 2.0.13 — getgrav
Grav anterior a 2.0.15 contiene una vulnerabilidad de escritura arbitraria de archivos en la validación de funciones desnudas de datos dinámicos de Blueprint, que utiliza una lista de denegación incompleta en lugar de una lista de permitidos positiva. Los atacantes con acceso de edición de página o de configuración de blueprint pueden invocar la función error_log a través de una directiva de datos para añadir payloads PHP a archivos accesibles por web, logrando ejecución remota de código.
| CVE | CVE-2026-75827 · CVE.org |
| CWE | CWE-94 |
| CVSS | Alto: 8.8 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Producto | grav |
| Afectado | todas las versiones hasta 2.0.13 (inclusive) |
| Parcheado | 2.0.15 y posteriores |
| Auth | ninguna (ver mapa de origen) |
| Lab | solo 127.0.0.1 · paquete de divulgación para proveedor/cliente, no un escáner |
La escritura arbitraria de archivos es error_log mediante data-options@ en un blueprint de formulario, no una acción de subida action=. HTTP es GET /poc-form y luego GET /poc-witness.txt.
GET/poc-formSemilla del lab: usuario admin + plugin Form + página 03.poc-form con data-options@: error_logGET /poc-form → Form::getBlueprint() → Blueprint::dynamicData → isSafeDynamicCall('error_log') true → error_log(witness, 3, poc-witness.txt)GET /poc-witness.txt → POCWitness75827El cuerpo de GET /poc-witness.txt contiene POCWitness75827. El HTML de inicio o un 404 vacío no es el testigo de escritura de archivo.
Haz esto primero: Actualiza grav a 2.0.15 o posterior.
Verificar tras la actualización
CVE-2026-75827-Abraxas-Labs.py contra la compilación parcheada: el testigo mapeado no debe aparecer.Si no puedes actualizar de inmediato
Apunta solo a http://127.0.0.1:8088 (o al loopback que hayas vinculado). No apuntes este script a internet.
python3 CVE-2026-75827-Abraxas-Labs.py
El éxito es el testigo anterior en el cuerpo de la respuesta. Un HTML genérico 200 no lo es.
Stack de loopback usado para reproducir. Imágenes oficiales a menos que un Dockerfile en esta carpeta compile desde el código fuente.
cd lab
docker compose up --force-recreate
Vincula el árbol del producto vulnerable junto a Compose si el YAML monta un directorio local (zip del plugin / etiqueta de código fuente de la tabla de versiones). No publiques nada excepto 127.0.0.1.
# CVE-2026-75827 (structured records)
- input: `https://nvd.nist.gov/vuln/detail/CVE-2026-75827`
- CWE: CWE-94
- published: 2026-08-18T12:19:32.553
## NVD description
Grav before 2.0.15 contains an arbitrary file write vulnerability in the Blueprint dynamic-data bare-function validation that uses an incomplete denylist instead of a positive allowlist. Attackers with page-edit or blueprint-config access can invoke the error_log function through a data directive to append PHP payloads to web-accessible files, achieving remote code execution.
## MITRE description
Grav before 2.0.15 contains an arbitrary file write vulnerability in the Blueprint dynamic-data bare-function validation that uses an incomplete denylist instead of a positive allowlist. Attackers with page-edit or blueprint-config access can invoke the error_log function through a data directive to append PHP payloads to web-accessible files, achieving remote code execution.
## Affected
- getgrav grav 0 affected, 2.0.15 unaffected
- OSV:
## References (JSON sources only)
- https://github.com/getgrav/grav/security/advisories/GHSA-f8wv-xp27-6gq7
- https://www.vulncheck.com/advisories/grav-before-arbitrary-file-write-via-error-log
- https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/75xxx/CVE-2026-75827.json
- https://nvd.nist.gov/vuln/detail/CVE-2026-75827
- https://github.com/advisories/GHSA-f8wv-xp27-6gq7
## GitHub advisory
Grav: Blueprint dynamic-data bare-function branch is denylist-gated and omits error_log, giving arbitrary file write
## 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),
Este paquete es para el proveedor, el propietario del sitio y laboratorios con licencia. El script se comunica con 127.0.0.1. Usarlo contra sistemas que no posees no está autorizado por Abraxas Labs. Sin garantía.