
Local-only proof-of-concept verifier for CVE-2026-100671, reproducing Grav Twig page-cache session-cookie disclosure and replay against loopback targets.
Minimal, local-only verifier for the Grav content-Twig/page-cache session
takeover fixed in Grav 2.0.25.
Advisories:
exploit.py is deliberately restricted to loopback targets:
127.0.0.1::1localhostIt refuses remote hosts, follows only loopback redirects, uses Python standard library HTTP clients, applies request timeouts, and never prints bearer tokens, cookies, or response bodies.
Use only against a Grav instance that you own or are explicitly authorized to test. The script creates a public proof page in the local lab. Remove that page after testing if the lab keeps content between runs.
get_cookie() reads the visiting administrator's session cookie server-side.The page-write account needs only the documented low-privilege write path. It
does not need api.pages.read, admin.*, or super-admin access. The victim
admin only needs to visit the public page once.
2.0.19 through 2.0.24: vulnerable with shipped default content-Twig
configuration.2.0.0 through 2.0.18, and 1.7.x: affected only when content Twig
was explicitly enabled.2.0.25: fixed.Run a local Grav lab with:
This two-file repository does not provision Grav, PHP, plugins, or accounts.
Set credentials through environment variables so they never enter source control:
export GRAV_BASE_URL=http://127.0.0.1:8080
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='local-writer-password'
export GRAV_ADMIN_USERNAME=admin
export GRAV_ADMIN_PASSWORD='local-admin-password'
python3 exploit.py
The base URL must remain loopback. The default route is random. A fixed route can be selected when comparing runs:
python3 exploit.py --route /cookie-proof-24
Expected vulnerable result:
page_create_status=201
admin_login_status=200
victim_page_status=200
attacker_page_status=200
cached_victim_cookie_visible_to_attacker=yes
admin_cookie_value_match=yes
replayed_cookie_api_status=200
result=VULNERABLE
The command exits 0 when the expected result is reproduced, 1 when it is
not reproduced, and 2 for a setup or transport error.
Run the same command against Grav 2.0.25:
python3 exploit.py --expect fixed
Expected fixed behavior: no attacker-visible session value and no successful
session replay. A clean fixed result exits 0 with --expect fixed.
Upgrade to Grav 2.0.25 or later. For older deployments, the advisory lists
these temporary controls:
security.twig_content.process_enabled: false; orsystem.pages.never_cache_twig: true;Do not treat HttpOnly, Secure, or SameSite as a fix: the vulnerable
cookie read occurs server-side during Twig rendering.
Reported by canhieu. This repository is a reproducibility aid for the already-public advisory, not a remote scanner or a credential-harvesting tool.