GHSA-mc5q-6hpj-rp7j: Infoleak

Published Sep 2, 2026
·
Updated

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 jsonencode/printr/yamlencode 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 configaccess 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. jsonencode/printr/yamlencode 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 $processtwig = $scantwigxss || $this->modularTwig() (Page.php:816), so a modular child's body Twig is sandboxed-rendered even with twigcontent.processenabled false (the default), while $scantwigxss 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, configaccess default false. First two lines show the gate holding, third is the bypass:

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

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

yaml user/config/plugins/email.yaml -- decoy secret mailer: { smtp: { password: CANARYSMTPPW8b3f1 } }

user/pages/70.parent/default.md --- title: Parent content: { items: '@self.modular' } template: modular ---

twig {# user/pages/70.parent/secret/default.md #} --- title: Secret template: modular/text --- {{ grav.offsetGet('config')|jsonencode }}

bash curl -s http://localhost/parent # body contains CANARYSMTPPW8b3f1

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 twigsandbox.allowedmethods for Grav\Common\Grav -- the legit uses are theme/getversion; offsetget is the raw-container reach. Closes the demonstrated path.

Sandbox-wide: make jsonencode/printr/yamlencode/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 Software

1 affected componentFixes available
composer/getgrav/grav<2.0.2
2.0.2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/getgrav/grav to a version that resolves this vulnerability.

    Fixed in 2.0.2
  2. Configuration

    Edit system/config/security.yaml to drop `offsetget` (and `__get`) from `twig_sandbox.allowed_methods` for `Grav\Common\Grav` so `grav['config']`/`grav.offsetGet('config')` cannot return the raw `Config` container. The material states allowed uses are `theme`/`getversion` and that `offsetget` is what enables raw-container reach.

    system/config/security.yaml (Twig sandbox) twig_sandbox.allowed_methods = Remove allow-listed primitive "offsetget" (and "__get") for "Grav\Common\Grav"

Event History

Sep 2, 2026
Advisory Published
via GitHub·09:41 PM
Data Sourced
via GitHub·09:41 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What level of access does an attacker need to exploit this issue?

The attacker needs the ability to place Twig expressions in editor content that will be processed by the Twig content sandbox. No user interaction is required once that content is rendered.

2

Does disabling configuration access in the sandbox prevent the leak?

No. When config access is disabled, the sandbox replaces config with a redacted facade and blocks Config methods, but grav.offsetGet('config') still returns the raw Config object and can be serialized through allow-listed filters.

3

What information can be exposed?

The bypass can dump the full configuration tree, including secrets under plugins.* such as SMTP credentials, API keys, and plugin database credentials. The security.salt is not exposed because it is stored outside the configuration tree.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203