CVE-2026-61690
Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits
Grav is a file-based Web platform. Prior to 2.0.1, Grav ZipArchiver::extract() in system/src/Grav/Common/Filesystem/ZipArchiver.php passes archives to ZipArchive::extractTo() without enforcing the system.gpm.archive uncompressed-size, file-count, or nesting-depth limits. Code using Archiver::create('zip') to extract an attacker-controlled archive can exhaust disk space or inodes and make the site unavailable. This issue is fixed in version 2.0.1.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
6.5
Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits
Grav is a file-based Web platform. Prior to 2.0.1, Grav ZipArchiver::extract() in system/src/Grav/Common/Filesystem/ZipArchiver.php passes archives to ZipArchive::extractTo() without enforcing the system.gpm.archive uncompressed-size, file-count, or nesting-depth limits. Code using Archiver::create('zip') to extract an attacker-controlled archive can exhaust disk space or inodes and make the site unavailable. This issue is fixed in version 2.0.1.
- Record
CVE-2026-61690- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-409
- Updated
- 2026-11-02
EUVDEUVD-2026-62534
6.5
Grav is a file-based Web platform. Prior to 2.0.1, Grav ZipArchiver::extract() in system/src/Grav/Common/Filesystem/ZipArchiver.php passes archives to ZipArchive::extractTo() without enforcing the system.gpm.archive uncompressed-size, file-count, or nesting-depth limits. Code using Archiver::create('zip') to extract an attacker-controlled archive can exhaust disk space or inodes and make the site unavailable. This issue is fixed in version 2.0.1.
- Record
EUVD-2026-62534- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-928x-9mpw-8h56
6.5
Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits
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:
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 < $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;
}
}
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):
// GHSA-2vcx-h8p2-9pg9: bound what extractTo() will write to disk.
$limits = $this->archiveLimits();
$size = $count = $depth = 0;
for ($i = 0; $i < $numFiles; $i++) {
$entryName = $zip->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
# 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
$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…
- Record
GHSA-928x-9mpw-8h56- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-409
- Updated
- 2026-11-02
NVD
6.5
Grav is a file-based Web platform. Prior to 2.0.1, Grav ZipArchiver::extract() in system/src/Grav/Common/Filesystem/ZipArchiver.php passes archives to ZipArchive::extractTo() without enforcing the system.gpm.archive uncompressed-size, file-count, or nesting-depth limits. Code using Archiver::create('zip') to extract an attacker-controlled archive can exhaust disk space or inodes and make the site unavailable. This issue is fixed in version 2.0.1.
- Record
CVE-2026-61690- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-409
- Updated
- 2026-11-02
OSVCVE-2026-61455
7.1
Grav before 2.0.1 Decompression Bomb via ZipArchiver
Grav before 2.0.1 contains a decompression bomb vulnerability in ZipArchiver::extract() that lacks limits on uncompressed size, file count, and nesting depth. Attackers can supply a crafted ZIP archive that expands to fill available disk space, causing denial of service by exhausting storage resources.
- Record
CVE-2026-61455- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-409
- Updated
- 2026-11-02
OSV
6.5
Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits
Grav is a file-based Web platform. Prior to 2.0.1, Grav ZipArchiver::extract() in system/src/Grav/Common/Filesystem/ZipArchiver.php passes archives to ZipArchive::extractTo() without enforcing the system.gpm.archive uncompressed-size, file-count, or nesting-depth limits. Code using Archiver::create('zip') to extract an attacker-controlled archive can exhaust disk space or inodes and make the site unavailable. This issue is fixed in version 2.0.1.
- Record
CVE-2026-61690- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-409
- Updated
- 2026-11-02
OSVGHSA-928x-9mpw-8h56
6.5
Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits
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:
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 < $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;
}
}
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):
// GHSA-2vcx-h8p2-9pg9: bound what extractTo() will write to disk.
$limits = $this->archiveLimits();
$size = $count = $depth = 0;
for ($i = 0; $i < $numFiles; $i++) {
$entryName = $zip->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
# 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
$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…
- Record
GHSA-928x-9mpw-8h56- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-409
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
getgrav/gravPackagist |
before 2.0.1 | 2.0.1 |
getgrav/gravPackagist From an advisory covering 2 CVEs |
before 2.0.1, plus listed versions 0.8.00.9.00.9.10.9.100.9.11 Show 45 moreShow less0.9.120.9.130.9.140.9.150.9.160.9.170.9.180.9.190.9.20.9.200.9.210.9.220.9.230.9.240.9.250.9.260.9.270.9.280.9.290.9.30.9.300.9.310.9.320.9.330.9.340.9.350.9.360.9.370.9.380.9.390.9.40.9.400.9.410.9.420.9.430.9.440.9.450.9.50.9.60.9.70.9.80.9.91.0.01.0.0-rc.11.0.0-rc.2 |
2.0.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, facts | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-61690 is a medium vulnerability (CVSS 6.5) in Getgrav Grav, published July 10, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.53%, the 45th percentile. Fixed in getgrav/grav 2.0.1.
References
github.com/getgrav/grav/security/advisories/GHSA-928x-9mpw-8h56
github.com/getgrav/grav/commit/1c1003cfcab5344203d6fde1aaa1f9a4ee3413ff
github.com/getgrav/grav/releases/tag/2.0.1
nvd.nist.gov/vuln/detail/CVE-2026-61690
github.com/advisories/GHSA-928x-9mpw-8h56
github.com/CVEProject/cvelistV5/tree/main/cves/2026/61xxx/CVE-2026-61455.json
Left out here: 285 versions and the rest of 2 source descriptions. The JSON record has them.