Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘
🐘 Packagist
Not in CISA KEV
MEDIUM severity

GHSA-mc5q-6hpj-rp7j

MEDIUMFix: getgrav/grav@7efe705

GHSA-mc5q-6hpj-rp7j is a medium-severity (CVSS 6.5) Code Injection vulnerability in getgrav/grav. O3 Security confirms whether GHSA-mc5q-6hpj-rp7j is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Grav: Twig sandbox config exfiltration via grav.offsetGet + dump filter (CVE-2026-44738 bypass)

Published
Sep 2, 2026
Updated
Sep 2, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 2, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🐘getgrav/grav

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The Twig content sandbox replaces config with the redacted SandboxConfig facade and strips Config::get/toArray from the method allowlist (GHSA-j274-39qw-32c9), so editor content can't read config secrets via config. That's bypassable: grav is the raw container, offsetget is allow-listed on it, so grav.offsetGet('config') returns the real Config. The allow-listed filters json_encode/print_r/yaml_encode then serialize it at the PHP level, never hitting the sandbox method gate, dumping the whole config tree including every plugins.* secret (SMTP creds, API keys, plugin DB creds). Incomplete fix for GHSA-j274-39qw-32c9. security.salt does not leak (it lives outside config).

Details

The documented path is blocked: config is the SandboxConfig facade (Twig.php:660) and the raw Config/Data method entries are stripped when config_access is false, so {{ config.get(...) }} returns the default and {{ grav.offsetGet('config').get(...) }} raises SecurityNotAllowedMethodError.

The bypass uses two allow-listed primitives the redaction doesn't cover:

  1. grav.offsetGet('config') returns the raw Config. The SandboxConfig facade replaces only the config variable, not grav['config']; offsetget is allow-listed on Grav\Common\Grav in system/config/security.yaml.
  2. json_encode/print_r/yaml_encode serialize the object inside the filter and never call GravSecurityPolicy::checkMethodAllowed (GravSecurityPolicy.php:65), so the stripped methods don't matter.

Bug class: object-dumping filters bypass the sandbox member gate. The same dump reaches page/pages/uri/user via their allow-listed accessors; config is the secret-bearing target.

Reachable below the publisher-Twig opt-in: a _-prefixed slug is modular (Page.php:228), and Page::content() sets $process_twig = $scan_twig_xss || $this->modularTwig() (Page.php:816), so a modular child's body Twig is sandboxed-rendered even with twig_content.process_enabled false (the default), while $scan_twig_xss stays false so the render-time XSS scan (GHSA-2c4f-86xc-cr74) is skipped. Any admin.pages author (or filesystem write to user/pages) exfiltrates config on a stock install. On a regular process.twig page the whole-tree dump trips the XSS scan and is blanked, but a targeted split/slice extraction of one subtree is XSS-clean and survives.

PoC

Sandboxed render, config_access default false. First two lines show the gate holding, third is the bypass:

{{ config.get('plugins.email.mailer.smtp.password', 'DENIED') }}
{# => DENIED #}
{{ grav.offsetGet('config').get('plugins.email.mailer.smtp.password') }}
{# => SecurityNotAllowedMethodError 'get' #}
{{ grav.offsetGet('config')|json_encode }}
{# => {...,"plugins":{"email":{"mailer":{"smtp":{"password":"CANARY..."}}}},...} #}

Stock-install reproduction (no user/config/security.yaml):

# user/config/plugins/email.yaml  -- decoy secret
mailer: { smtp: { password: CANARY_SMTP_PW_8b3f1 } }
# user/pages/70.parent/default.md
---
title: Parent
content: { items: '@self.modular' }
template: modular
---
{# user/pages/70.parent/_secret/default.md #}
---
title: Secret
template: modular/text
---
{{ grav.offsetGet('config')|json_encode }}
curl -s http://localhost/parent   # body contains CANARY_SMTP_PW_8b3f1

logs/security.log shows no sandbox block and no XSS scan for the route. Verified on Grav 2.0.1 (6f619f0ae), PHP 8.4.22, Twig 3.26.1-DEV.

Impact

A page author (admin.pages, no admin/super) reads the entire config tree: plugin SMTP credentials, API keys, plugin DB credentials. Read-only. Default install; the modular path needs no Twig opt-in.

Fix

system/config/security.yaml: drop offsetget (and __get) from twig_sandbox.allowed_methods for Grav\Common\Grav -- the legit uses are theme/getversion; offsetget is the raw-container reach. Closes the demonstrated path.

Sandbox-wide: make json_encode/print_r/yaml_encode/string refuse non-allow-listed objects when $env->isSandboxed() (mirror the Closure-only guard Twig applies to map/filter/reduce). Closes the class for page/pages/uri/user too.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistgetgrav/gravall versions2.0.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for getgrav/grav. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update getgrav/grav to 2.0.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-mc5q-6hpj-rp7j is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-mc5q-6hpj-rp7j is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-mc5q-6hpj-rp7j. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary The Twig content sandbox replaces `config` with the redacted `SandboxConfig` facade and strips `Config::get`/`toArray` from the method allowlist (GHSA-j274-39qw-32c9), so editor content can't read config secrets via `config`. That's bypassable: `grav` is the raw container, `offsetget` is allow-listed on it, so `grav.offsetGet('config')` returns the real `Config`. The allow-listed filters `json_encode`/`print_r`/`yaml_encode` then serialize it at the PHP level, never hitting the sandbox method gate, dumping the whole config tree including every `plugins.*` secret (SMTP creds, API k
O3 Security · Impact-Aware SCA

Is GHSA-mc5q-6hpj-rp7j in your dependencies?

O3 detects GHSA-mc5q-6hpj-rp7j across Packagist dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

GHSA-mc5q-6hpj-rp7j: getgrav/grav… | O3 Security