Lack of data validation In getgrav/grav
Description
Grav Vulnerable to Privilege Escalation via Missing Server-Side Validation of groups/access
Bug Report: Registration Privilege Escalation via Missing Server-Side Validation of groups/access
Summary
The Login::register() method in the Login plugin accepts attacker-controlled groups and access fields from the registration POST data without server-side validation. When registration is enabled and groups or access are included in the configured allowed fields list, an unauthenticated user can self-register with admin.super privileges by injecting these fields into the registration request.
This is a missing server-side validation issue — the only defense is a config-level fields allowlist, which is an admin-facing setting, not a hardcoded security boundary.
Affected Component
File: user/plugins/login/classes/Login.php, lines 246-306
Method: Login::register()
Validation: Login::validateField(), lines 363-432
Plugin: Login Plugin 3.8.0
Grav: 1.8.0-beta.29
Root Cause
In register() (lines 254-267), the groups and access fields are only set to config defaults if they are not already present in the input data:
// Line 254-260 if (!isset($data['groups'])) { $groups = (array) $this->config->get('plugins.login.user_registration.groups', []); if (count($groups) > 0) { $data['groups'] = $groups; } } ...
If an attacker includes groups or access in the POST body, the !isset() check passes and the config defaults are skipped. The attacker's values flow through unchanged.
Later (lines 298-303), these values are assigned directly to the user object:
if (isset($data['groups'])) { $user->groups = $data['groups']; // attacker-controlled } if (isset($data['access'])) { $user->access = $data['access']; // attacker-controlled } $user->save();
The validateField() method (lines 363-432) has a switch statement that only validates: username, password, password2, email, permissions, state, and language. The groups and access fields pass through the default case with no validation at all.
Precondition
Registration must be enabled with groups and/or access in the configured allowed fields:
# user/config/plugins/login.yaml user_registration: enabled: true fields: - username - password - email - fullname...
This is a configuration the admin UI allows without any warning. An admin adding groups to let users pick a non-privileged group (e.g., editors) unknowingly exposes the escalation path, since there is no validation constraining which groups can be selected.
Proof of Concept
Malicious registration request (unauthenticated):
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][email protected]" \ --data-urlencode "data[fullname]=Attacker" \ --data-urlencode "data[groups][]=admins" \ --data-urlencode "data[access][admin][login]=true" \...
Resulting account file (user/accounts/attacker.yaml):
email: [email protected] fullname: Attacker groups: - admins access: admin: login: true super: true...
The attacker can then log into /admin with full super-admin privileges.
Impact
Severity: Critical (when precondition is met)
Vector: Unauthenticated → Super Admin
Escalation: Full admin panel access, which chains to RCE via known admin vectors https://github.com/getgrav/grav/security/advisories/GHSA-4fg4-8cr8-326m or Plugin Upload
Precondition: Registration enabled with groups or access in allowed fields — a configuration the admin UI permits without warning
Environment
Grav Core: 1.8.0-beta.29
Login Plugin: 3.8.0
PHP: 8.4.11
Credits
Jonathan Dersch at Hacking Cult GmbH https://hackingcult.de/
Maintainer note — fix applied (2026-04-24)
Fixed in grav-plugin-login 3.8.2 (commit 3d419a0). On the Grav 2.0 line, the login plugin is pinned at >=3.8.2 by admin2's blueprints.yaml, so sites running admin2 with Grav 2.0.0-beta.2 pick the fix up automatically.
What changed: the registration form handler now explicitly skips the groups and access privilege fields in the per-field input loop — even if an administrator added them to user_registration.fields. A warning is logged on any attempted injection. Server-side default_values, invitations, and the user_registration.{groups,access} config remain the sole sources of those values.
Files:
login.php — form handler privilege-field strip.
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Package | Affected version | Patched versions |
|---|---|---|---|
packagist | getgrav/grav | 2.0.0-beta.2 |
Aliases
References