Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘 Packagist
Not in CISA KEV

CVE-2026-108359 — phpspreadsheet

Fix: PHPOffice/PhpSpreadsheet@2999938

CVE-2026-108359 is a Uncontrolled Resource Consumption vulnerability in phpoffice/phpspreadsheet. A fix is available for phpoffice/phpspreadsheet — see the affected versions and patch details below.

PhpSpreadsheet ODS Reader: Unbounded number-rows-repeated → CPU & Memory Exhaustion (DoS)

Also known asGHSA-j2w7-387v-m2rq
Published
Updated
Affected
5 pkgs
Patched
5 / 5
Exploits
None indexed
Exploitation data as of Oct 27, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs35th percentile — riskier than 35% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

5 pkgs affected
🐘phpoffice/phpspreadsheet🐘phpoffice/phpspreadsheet🐘phpoffice/phpspreadsheet🐘phpoffice/phpspreadsheet🐘phpoffice/phpspreadsheet

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

When PhpSpreadsheet loads an OpenDocument Spreadsheet (.ods), Ods::processTableRow() reads the table:number-rows-repeated attribute of a <table:table-row> with an unchecked (int) cast, then materialises one cell per repeated row. This value is completely unbounded, so a single ~1.4 KB file can instruct the reader to create up to millions of cells, exhausting memory (fatal OOM). No authentication, no special configuration, and the default read filter are required - any application that loads a user-supplied .ods is affected.


Affected versions

The number-rows-repeated expansion has existed in the ODS reader since its inception (pre-dating the current release branches), so all maintained lines are affected.

BranchAffected
1.x≤ 1.30.6
2.0–2.1≤ 2.1.18
2.2–2.4≤ 2.4.7
3.x≤ 3.10.7
4.x–5.x≤ 5.9.0 (latest)

Root cause (source → sink)

src/PhpSpreadsheet/Reader/Ods.php:

// line 816 — row repeat, unchecked (int) cast
$rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
...
// line 1151 — row materialisation loop: NO upper bound
for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
    $rID = $rowID + $rowAdjust;
    $cell = $spreadsheet->getActiveSheet()->getCell($columnID . $rID);  // allocates a Cell
    $cell->setValueExplicit($dataValue, $type);
    // ... no bounds check on $rowAdjust or $rID ...
}

Why columns are NOT vulnerable (already bounded):

The column-header loop at line 1326 shows the correct pattern with bounds checking:

// line 1326 (processTableColumnHeader): BOUNDED correctly
for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0 && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT; --$rowRepeats2) {
    // HAS BOUNDS CHECK: && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT
}

Additionally, the cell-data column loop at line 1141 calls getCell() which throws exception at Coordinate.php:448 when column exceeds MAX_COLUMN_INT (XFD / 16,384):

// Coordinate.php line 448
if ($columnIndex > self::MAX_COLUMN_INT) {
    throw new Exception('Column string index can not be beyond XFD');
}

Result: Columns are capped at 16,384. Only rows are unbounded.


Impact

Memory exhaustion (single valued cell, repeated many rows):
A non-null cell value with a huge number-rows-repeated attribute materialises millions of Cell objects → PHP fatal Allowed memory size ... exhausted.

Example:

  • Payload: <table:table-row table:number-rows-repeated="5000000"> (5 million rows)
  • File size: 1.4 KB
  • Memory consumed: ~256 MB (one Cell object per row)
  • Result: OOM at default memory limit

Reproduction

Vulnerable payload (causes OOM):

<table:table table:name="Sheet1">
 <table:table-row table:number-rows-repeated="5000000">
  <table:table-cell office:value-type="float" office:value="1">
   <text:p>1</text:p>
  </table:table-cell>
 </table:table-row>
</table:table>

Not vulnerable (for reference - throws exception):

<table:table table:name="Sheet1">
 <table:table-row>
  <table:table-cell office:value-type="float" office:value="1"
      table:number-columns-repeated="1000000000">
   <text:p>1</text:p>
  </table:table-cell>
 </table:table-row>
</table:table>

This column-repeat example throws Column string index can not be beyond XFD exception (bounded defense, already in place).

Affected Packages

5 total 5 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistphpoffice/phpspreadsheet≥ 4.0.0&&< 5.10.05.10.0composer require phpoffice/phpspreadsheet:^5.10.0
🐘Packagistphpoffice/phpspreadsheet≥ 3.3.0&&< 3.10.83.10.8composer require phpoffice/phpspreadsheet:^3.10.8
🐘Packagistphpoffice/phpspreadsheet≥ 2.2.0&&< 2.4.82.4.8composer require phpoffice/phpspreadsheet:^2.4.8
🐘Packagistphpoffice/phpspreadsheet≥ 2.0.0&&< 2.1.192.1.19composer require phpoffice/phpspreadsheet:^2.1.19
🐘Packagistphpoffice/phpspreadsheetall versions1.30.7composer require phpoffice/phpspreadsheet:^1.30.7

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for phpoffice/phpspreadsheet, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update phpoffice/phpspreadsheet to 5.10.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-108359 is resolved across your whole dependency graph.

  3. Workarounds

    Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.

Frequently Asked Questions

## Summary When PhpSpreadsheet loads an OpenDocument Spreadsheet (`.ods`), `Ods::processTableRow()` reads the `table:number-rows-repeated` attribute of a `<table:table-row>` with an unchecked `(int)` cast, then materialises one cell per repeated row. This value is **completely unbounded**, so a **single ~1.4 KB file** can instruct the reader to create up to **millions of cells**, exhausting memory (fatal OOM). No authentication, no special configuration, and the default read filter are required - any application that loads a user-supplied `.ods` is affected. --- ## Affected versions Th
O3 Security · Impact-Aware SCA

Is CVE-2026-108359 in your dependencies?

Find it across Packagist, including transitive dependencies.

CVE-2026-108359: phpspreadsheet — Fixed in 5.10.0