Sep 17, 2026·6 min read·1 visit
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.
A critical unauthenticated arbitrary module import vulnerability in the djust framework before version 1.0.7 allows remote attackers to execute arbitrary code by exploiting unsafe Python reflection during LiveView connection mounting.
CVE-2026-57173 (GHSA-hcwq-8wjf-3gcr) represents a critical resource allocation validation vulnerability in the vLLM inference engine. Prior to version 0.24.0, vLLM's multimodal chat completions pipeline failed to enforce maximum audio decode duration limits. Unauthenticated remote attackers can exploit this to perform an audio decompression bomb attack, causing massive memory allocations that trigger immediate system Out-Of-Memory (OOM) crashes and service termination.
Grav CMS before v2.0.1 contains a security bypass vulnerability in its blueprint validation logic. The XSS detection routine, Security::detectXss(), was executed on raw page contents prior to Twig engine processing. When Twig processing is enabled for editor-authored page content, an attacker can dynamically reconstruct harmful HTML elements, attributes, or protocols using string concatenation (e.g. `{{ 'on' ~ 'error' }}`). When compiled, the benign source converts into active XSS payloads, which are rendered to the client browser via raw filters. This vulnerability was resolved in version 2.0.1 by adding a post-render validation backstop.
An OAuth resource spoofing vulnerability in the rmcp crate prior to 2.0.0 allows a malicious Model Context Protocol (MCP) server to spoof protected resource metadata. By presenting metadata pointing to a legitimate resource and authorization server, the attacker can trick the client into completing the authentication flow and subsequently sending the authorized token back to the malicious server.
CVE-2026-63128 is a high-severity uncontrolled resource consumption vulnerability in the Model Context Protocol (MCP) official Rust SDK (the rmcp crate) prior to version 2.0.0. An unauthenticated attacker can exploit this vulnerability by sending malformed or mismatching handshake requests to the stateful Streamable HTTP server, causing persistent memory allocation without cleanup. This results in an unbounded memory leak and lock contention that ultimately leads to complete denial of service.
A cross-site scripting (XSS) vulnerability was identified in @nuxtjs/mdc prior to version 0.22.1. Gaps in the HTML/SVG attribute verification and URL protocol parsing allow unauthenticated remote attackers to bypass the application's sanitization routines. By embedding malicious SVG links or data-encoded iframe elements within Markdown, attackers can execute arbitrary JavaScript in the victim's browser context.