Sep 17, 2026·6 min read·61 visits
An unvalidated ZIP archive extraction flaw in Grav CMS allows administrators to crash the server or fill up disk space using malicious zip files.
A denial-of-service (DoS) and resource exhaustion vulnerability exists in Grav CMS prior to version 2.0.0. The package installer decompressor fails to validate ZIP archive limits before extraction, allowing authenticated administrators to cause disk exhaustion, inode exhaustion, or process termination.
The Grav Package Manager (GPM) features an administrative installation portal called "Direct Install" designed to facilitate the manual upload and extraction of extension packages, such as plugins and templates, packaged as standard ZIP files.
Under the hood, these manual uploads are handled by the static Installer::unZip() method in the Grav\Common\GPM\Installer class. This interface exposes a high-exposure attack surface that accepts compressed archives directly from administrative endpoints without enforcing resource boundaries during extraction.
In vulnerable deployments of Grav CMS (versions prior to 2.0.0-rc.11), the unZip function processes uploaded archives directly through native PHP ZipArchive functions without inspecting entry counts, cumulative file sizes, or nested depth parameters. This design flaw introduces substantial risk of denial-of-service, local resource exhaustion, and critical process termination.
The root cause of CVE-2026-59193 resides in the absence of validation bounds inside the Installer::unZip() method in system/src/Grav/Common/GPM/Installer.php before calling ZipArchive::extractTo(). The application retrieves the uploaded ZIP file, verifies that paths do not contain traversal patterns such as ../, and then directly decompresses the payload into the destination folder structure.
Without limits on the raw size of uncompressed content, the execution flow is susceptible to a Decompression Bomb (CWE-409). High data-amplification archives using the DEFLATE algorithm compress repeating streams to microscopic file footprints but inflate to gigabytes of data upon extraction, immediately consuming target partition space.
Similarly, the absence of limits on file headers allows inode exhaustion attacks. By compiling tens of thousands of empty or microscopic files inside a single ZIP payload, an attacker can consume the filesystem's total pool of allocated inodes, breaking core operational writes for databases and logs.
Furthermore, the recursive deletion function Folder::delete($destination) in Grav\Common\Filesystem\Folder is vulnerable to Uncontrolled Recursion (CWE-674). If the system attempts to purge or clean up a failed extraction containing hundreds of levels of empty directories, the recursive traversal consumes the thread's memory limit or exceeds the maximum PHP stack size, triggering an abrupt crash that leaves intermediate files permanent on disk.
To understand the technical progression of the vulnerability, we analyze the vulnerable and patched pathways. In the pre-patch state, Installer::unZip() processed files solely through simple sanity checks and path traversal mitigations. Once the name of each file passed safety checks, it immediately invoked the extraction utility.
// Vulnerable Implementation - system/src/Grav/Common/GPM/Installer.php
// The extraction was performed directly after loop validation with zero size bounds
$zip = new ZipArchive();
if ($zip->open($zip_file) === true) {
$numFiles = $zip->numFiles;
for ($i = 0; $i < $numFiles; $i++) {
$entryName = (string) $zip->getNameIndex($i);
if (!self::isSafeArchiveEntry($entryName)) {
self::$error = self::ZIP_EXTRACT_ERROR;
$zip->close();
return false;
}
}
$zip->extractTo($destination);
$zip->close();
}The remediation introduces explicit checks designed to count total directory segments, aggregate the sizes declared in directory records, and verify that the file-count meets system limits before any extraction steps occur on disk.
// Patched Implementation - system/src/Grav/Common/GPM/Installer.php
// Limits are checked *prior* to extraction to prevent files from writing to disk.
[$maxSize, $maxFiles, $maxDepth] = self::archiveLimits();
$numFiles = $zip->numFiles;
if ($maxFiles > 0 && $numFiles > $maxFiles) {
self::$error = self::ZIP_LIMITS_ERROR;
$zip->close();
return false;
}
$totalSize = 0;
for ($i = 0; $i < $numFiles; $i++) {
$entryName = (string) $zip->getNameIndex($i);
if (!self::isSafeArchiveEntry($entryName)) {
self::$error = self::ZIP_EXTRACT_ERROR;
$zip->close();
return false;
}
if ($maxDepth > 0) {
// Count directory segment counts using regex splits on slashes
$depth = count(array_filter(preg_split('#[\\\\/]+#', trim($entryName, '/\\'))));
if ($depth > $maxDepth) {
self::$error = self::ZIP_LIMITS_ERROR;
$zip->close();
return false;
}
}
if ($maxSize > 0) {
$stat = $zip->statIndex($i);
if (is_array($stat) && isset($stat['size'])) {
$totalSize += (int) $stat['size'];
if ($totalSize > $maxSize) {
self::$error = self::ZIP_LIMITS_ERROR;
$zip->close();
return false;
}
}
}
}Exploiting CVE-2026-59193 requires valid administrative credentials with privileges corresponding to the admin.super permission flag. The GPM manual direct-install interface is unavailable to standard users or unauthenticated external entities.
An administrative attacker prepares a ZIP archive tailored to one of three targeting methodologies: resource amplification, inode exhaustion, or call-stack overflow. For a typical decompression bomb, the attacker constructs an archive containing 1 gigabyte of null bytes compressed using deflate compression parameters, yielding a file size of roughly 1 megabyte. For stack-based termination, an archive is compiled with directory pathways configured with deep nested subdirectory sequences.
Upon navigating to the Direct Install dashboard, the attacker submits the crafted archive through the upload endpoint. The system receives the file and triggers the unZip function. The extraction proceeds immediately, leading to disk full status, inode consumption, or thread failure depending on the targeted methodology.
Security engineers analyzing this fix must evaluate edge cases that present paths for potential bypass. A core concern lies in the discrepancy between Central Directory Headers and Local File Headers inside ZIP file layouts. The current patch reads entry sizes via $zip->statIndex($i), which queries the Central Directory. If an attacker forges the Central Directory headers to list extremely small uncompressed sizes while the actual Local File Headers contain large file streams, ZipArchive::extractTo() might extract the large file data anyway, bypassing the size-checking logic.
Additionally, integer overflow represents a critical risk on 32-bit PHP installations. Cumulative calculations of $totalSize using explicit (int) cast types can wrap around into negative or unexpected integer scopes if cumulative file sizes exceed the 32-bit limits. This behavior could cause the cumulative size check to evaluate to a negative value, rendering the comparison to $maxSize structurally ineffective.
Furthermore, the logic for determining file structure nesting levels relies entirely on splitting characters via the preg_split('#[\\\\/]+#', ...) pattern. Variation in filesystem path translation on Windows versus POSIX targets, combined with unexpected unicode character representations or locale settings, may allow files with highly complex directory trees to escape the counting logic while still triggering deep recursive directory paths on disk.
Mitigation of CVE-2026-59193 relies on updating the underlying application to version 2.0.0 or utilizing release build 2.0.0-rc.11. The update introduces system-level configuration parameters to constrain zip parsing automatically.
If manual upgrading cannot be performed immediately, administrators must secure the system configuration manually by modifying system/config/system.yaml or creating custom override configs. Enforcing safe properties such as setting maximum allowable file counts and sizes will block anomalous extraction requests during runtime execution.
Furthermore, system administrators must limit access to the administrative dashboard, specifically restricting admin.super credentials to validated operations. Disabling third-party, non-proxied package resources via official_gpm_only: true provides another layer of security against untrusted files.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Grav CMS getgrav | >= 1.0.0, < 2.0.0 | 2.0.0-rc.11 / 2.0.0 |
Grav CMS getgrav | 2.0.0-beta.1 - 2.0.0-rc.10 | 2.0.0-rc.11 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-409, CWE-674 |
| Attack Vector | Network (Authenticated) |
| CVSS v3.1 | 4.9 (Medium) |
| EPSS Score | 0.006 (0.60%) |
| Exploit Status | Proof of Concept / Technical Tests |
| CISA KEV | Not Listed |
| Impact | Denial of Service (Disk space exhaustion, Inode exhaustion, Process Crash) |
The product does not sufficiently restrict the size or complexity of compressed data before decompressing it, allowing attackers to exhaust system resources.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.