
Prova de conceito e laboratório para CVE-2026-75827, uma escrita arbitrária de arquivo no Grav via error_log de dados dinâmicos do Blueprint, com script de reprodução e laboratório Docker.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · CVE-2026-75827
grav 2.0.13 — getgrav
O Grav anterior à versão 2.0.15 contém uma vulnerabilidade de escrita arbitrária de arquivos na validação de funções bare de dados dinâmicos do Blueprint, que utiliza uma denylist incompleta em vez de uma allowlist positiva. Atacantes com acesso de edição de página ou de configuração de blueprint podem invocar a função error_log através de uma diretiva de dados para anexar payloads PHP a arquivos acessíveis pela web, alcançando execução 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 |
| Produto | grav |
| Afetado | todas as versões até 2.0.13 (inclusive) |
| Corrigido | 2.0.15 e posteriores |
| Autenticação | nenhuma (ver mapa de origem) |
| Lab | apenas 127.0.0.1 · pacote de divulgação para fornecedor/cliente, não é um scanner |
A escrita arbitrária de arquivos é via error_log através de data-options@ em um blueprint de formulário, não uma ação de upload=. O HTTP é GET /poc-form e depois GET /poc-witness.txt.
GET/poc-formLab seed: admin user + Form plugin + page 03.poc-form with 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 → POCWitness75827O corpo de GET /poc-witness.txt contém POCWitness75827. HTML da home ou 404 vazio não é o witness de escrita de arquivo.
Faça isto primeiro: Atualize o grav para 2.0.15 ou mais recente.
Verifique após a atualização
CVE-2026-75827-Abraxas-Labs.py contra a build corrigida: o witness mapeado não deve aparecer.Se não puder atualizar imediatamente
Alveje apenas http://127.0.0.1:8088 (ou o loopback que você vinculou). Não aponte este script para a internet.
python3 CVE-2026-75827-Abraxas-Labs.py
Sucesso é o witness acima no corpo da resposta. HTML 200 genérico não é.
Stack de loopback usada para reproduzir. Imagens oficiais, a menos que um Dockerfile nesta pasta faça build a partir do código-fonte.
cd lab
docker compose up --force-recreate
Vincule a árvore do produto vulnerável ao lado do Compose se o YAML montar um diretório local (zip do plugin / tag de origem da tabela de versões). Não publique nada exceto 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 pacote é para o fornecedor, o proprietário do site e laboratórios licenciados. O script se comunica com 127.0.0.1. Usá-lo contra sistemas que você não possui não é autorizado pela Abraxas Labs. Sem garantias.