CVE-2026-108359
highpriority · track · 0EPSS 0.004 · 35 %ileUSEPSS · scored 2026-10-27
PhpSpreadsheet is a pure PHP library for reading and writing spreadsheet files. Prior to 1.30.7, 2.1.19, 2.4.8, 3.10.8, and 5.10.0, Ods::processTableRow in src/PhpSpreadsheet/Reader/Ods.php accepts an unbounded table:number-rows-repeated attribute from an ODS table row. When the row contains a value, the reader materializes a cell for every repetition without checking the spreadsheet row limit, so a small attacker-controlled ODS file can consume substantial CPU and memory and terminate the PHP worker. Column repetition is separately bounded and is not part of this vulnerability. This issue is fixed in versions 1.30.7, 2.1.19, 2.4.8, 3.10.8, and 5.10.0.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode⚠ sources disagree · medium–high · 6.6–8.7
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (5)
Products and version ranges known to be affected.
advisories (1)
Vendor and ecosystem advisories describing this.
- GHSA-j2w7-387v-m2rqPhpSpreadsheet ODS Reader: Unbounded number-rows-repeated → CPU & Memory Exhaustion (DoS)2026-10-27ghsa · osvUS
## 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. | Branch | Affected | |--------|----------| | 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`: ```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: ```php // 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): ```php // 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):** ```xml <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):** ```xml <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).
sources
Which sources assert this record, and when.
references (11) nvd
External citations — patches, advisories, analysis.
- https://github.com/PHPOffice/PhpSpreadsheet/commit/299993894dce37aa3b64aa23e9a3f4bb77ab0e66
- https://github.com/PHPOffice/PhpSpreadsheet/commit/2de29384aa11223ee08136c0e6a9e9981bc1a2eb
- https://github.com/PHPOffice/PhpSpreadsheet/commit/980b5c294493856f1a25754f1e8a1d2185b93738
- https://github.com/PHPOffice/PhpSpreadsheet/commit/9a0b48622ebfe93fa48cb9bc09ce57303bb07af9
- https://github.com/PHPOffice/PhpSpreadsheet/commit/da13f40e8760d599b0e8a296017d4b028b20e86c
- https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/1.30.7
- https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.1.19
- https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.4.8
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan