From d95bc290beb137d4118095b96f62ec47e0205cec Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 24 Sep 2024 05:49:02 -0700 Subject: [PATCH] Backport Security Patch --- phpstan-baseline.neon | 2 +- src/PhpSpreadsheet/Reader/Xlsx.php | 14 +++- src/PhpSpreadsheet/Worksheet/BaseDrawing.php | 2 +- src/PhpSpreadsheet/Worksheet/Drawing.php | 70 +++++++++++++----- src/PhpSpreadsheet/Writer/Html.php | 16 ++-- src/PhpSpreadsheet/Writer/Xls.php | 2 +- src/PhpSpreadsheet/Writer/Xlsx.php | 10 ++- .../Writer/Xlsx/ContentTypes.php | 22 ++++-- .../Reader/Xlsx/URLImageTest.php | 28 ++++++- .../Html/ExtendForChartsAndImagesTest.php | 45 ++++++++++- .../Writer/Html/ImageEmbedTest.php | 45 +++++++++++ tests/data/Reader/XLSX/urlImage.bad.dontuse | Bin 0 -> 10114 bytes tests/data/Reader/XLSX/urlImage.notfound.xlsx | Bin 0 -> 10116 bytes 13 files changed, 214 insertions(+), 42 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Writer/Html/ImageEmbedTest.php create mode 100644 tests/data/Reader/XLSX/urlImage.bad.dontuse create mode 100644 tests/data/Reader/XLSX/urlImage.notfound.xlsx diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 120a4f4cef..e107b26679 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -287,7 +287,7 @@ parameters: - message: "#^Offset 'mime' does not exist on array\\{\\}\\|array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\}\\.$#" - count: 2 + count: 1 path: src/PhpSpreadsheet/Writer/Html.php - diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 1c33fd6811..5f63743d08 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1291,7 +1291,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $hfImages[$shapeId]->setName((string) $imageData['title']); } - $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false); + $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false, $zip); $hfImages[$shapeId]->setResizeProportional(false); $hfImages[$shapeId]->setWidth($style['width']); $hfImages[$shapeId]->setHeight($style['height']); @@ -1401,7 +1401,8 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $images[$embedImageKey], - false + false, + $zip ); } else { $linkImageKey = (string) self::getArrayItem( @@ -1412,6 +1413,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); $objDrawing->setPath($url); } + if ($objDrawing->getPath() === '') { + continue; + } } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); @@ -1486,7 +1490,8 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $images[$embedImageKey], - false + false, + $zip ); } else { $linkImageKey = (string) self::getArrayItem( @@ -1497,6 +1502,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); $objDrawing->setPath($url); } + if ($objDrawing->getPath() === '') { + continue; + } } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index 5001346ad5..49e2eff104 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -220,7 +220,7 @@ public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = f { if ($this->worksheet === null) { // Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet - if ($worksheet !== null) { + if ($worksheet !== null && !($this instanceof Drawing && $this->getPath() === '')) { $this->worksheet = $worksheet; $this->worksheet->getCell($this->coordinates); $this->worksheet->getDrawingCollection()->append($this); diff --git a/src/PhpSpreadsheet/Worksheet/Drawing.php b/src/PhpSpreadsheet/Worksheet/Drawing.php index 7d957539ef..bb65db2b97 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -106,40 +106,72 @@ public function getPath() */ public function setPath($path, $verifyFile = true, $zip = null) { - if ($verifyFile && preg_match('~^data:image/[a-z]+;base64,~', $path) !== 1) { - // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 - if (filter_var($path, FILTER_VALIDATE_URL)) { - $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 = file_get_contents($path); + $this->isUrl = false; + if (preg_match('~^data:image/[a-z]+;base64,~', $path) === 1) { + $this->path = $path; + + return $this; + } + + $this->path = ''; + // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 + if (filter_var($path, FILTER_VALIDATE_URL)) { + if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) { + throw new PhpSpreadsheetException('Invalid protocol for linked drawing'); + } + // Implicit that it is a URL, rather store info than running check above on value in other places. + $this->isUrl = true; + $imageContents = @file_get_contents($path); + if ($imageContents !== false) { $filePath = tempnam(sys_get_temp_dir(), 'Drawing'); if ($filePath) { - file_put_contents($filePath, $imageContents); - if (file_exists($filePath)) { - $this->setSizesAndType($filePath); + $put = @file_put_contents($filePath, $imageContents); + if ($put !== false) { + if ($this->isImage($filePath)) { + $this->path = $path; + $this->setSizesAndType($filePath); + } unlink($filePath); } } - } elseif (file_exists($path)) { - $this->path = $path; - $this->setSizesAndType($path); - } elseif ($zip instanceof ZipArchive) { - $zipPath = explode('#', $path)[1]; - if ($zip->locateName($zipPath) !== false) { + } + } elseif ($zip instanceof ZipArchive) { + $zipPath = explode('#', $path)[1]; + $locate = @$zip->locateName($zipPath); + if ($locate !== false) { + if ($this->isImage($path)) { $this->path = $path; $this->setSizesAndType($path); } - } else { - throw new PhpSpreadsheetException("File $path not found!"); } } else { - $this->path = $path; + $exists = @file_exists($path); + if ($exists !== false && $this->isImage($path)) { + $this->path = $path; + $this->setSizesAndType($path); + } + } + if ($this->path === '' && $verifyFile) { + throw new PhpSpreadsheetException("File $path not found!"); } return $this; } + private function isImage(string $path): bool + { + $mime = (string) @mime_content_type($path); + $retVal = false; + if (str_starts_with($mime, 'image/')) { + $retVal = true; + } elseif ($mime === 'application/octet-stream') { + $extension = pathinfo($path, PATHINFO_EXTENSION); + $retVal = in_array($extension, ['bin', 'emf'], true); + } + + return $retVal; + } + /** * Get isURL. */ diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 978d1764c1..66a0ec89b6 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -612,6 +612,9 @@ private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): s [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($worksheet, $row); foreach ($worksheet->getDrawingCollection() as $drawing) { + if ($drawing instanceof Drawing && $drawing->getPath() === '') { + continue; + } $anyfound = true; $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates()); $imageCol = Coordinate::columnIndexFromString($imageTL[0]); @@ -687,7 +690,7 @@ private function writeImageInCell(Worksheet $worksheet, $coordinates) } $filedesc = $drawing->getDescription(); $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image'; - if ($drawing instanceof Drawing) { + if ($drawing instanceof Drawing && $drawing->getPath() !== '') { $filename = $drawing->getPath(); // Strip off eventual '.' @@ -706,12 +709,15 @@ private function writeImageInCell(Worksheet $worksheet, $coordinates) $imageData = self::winFileToUrl($filename, $this->isMPdf); if ($this->embedImages || substr($imageData, 0, 6) === 'zip://') { + $imageData = 'data:,'; $picture = @file_get_contents($filename); if ($picture !== false) { - $imageDetails = getimagesize($filename) ?: []; - // base64 encode the binary data - $base64 = base64_encode($picture); - $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; + $mimeContentType = (string) @mime_content_type($filename); + if (substr($mimeContentType, 0, 6) === 'image/') { + // base64 encode the binary data + $base64 = base64_encode($picture); + $imageData = 'data:' . $mimeContentType . ';base64,' . $base64; + } } } diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index 983414fccc..e0480a5a36 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -486,7 +486,7 @@ private function processDrawing(BstoreContainer &$bstoreContainer, Drawing $draw private function processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void { - if ($drawing instanceof Drawing) { + if ($drawing instanceof Drawing && $drawing->getPath() !== '') { $this->processDrawing($bstoreContainer, $drawing); } elseif ($drawing instanceof MemoryDrawing) { $this->processMemoryDrawing($bstoreContainer, $drawing, $drawing->getRenderingFunction()); diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 6ed12d4aa1..601f0f5c4c 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -495,7 +495,9 @@ public function save($filename, int $flags = 0): void // Media foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { - $zipContent['xl/media/' . $image->getIndexedFilename()] = file_get_contents($image->getPath()); + if ($image->getPath() !== '') { + $zipContent['xl/media/' . $image->getIndexedFilename()] = file_get_contents($image->getPath()); + } } } @@ -511,6 +513,9 @@ public function save($filename, int $flags = 0): void if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) { $imageContents = null; $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath(); + if ($imagePath === '') { + continue; + } if (strpos($imagePath, 'zip://') !== false) { $imagePath = substr($imagePath, 6); $imagePathSplitted = explode('#', $imagePath); @@ -712,6 +717,9 @@ private function processDrawing(WorksheetDrawing $drawing) { $data = null; $filename = $drawing->getPath(); + if ($filename === '') { + return null; + } $imageData = getimagesize($filename); if (!empty($imageData)) { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php index 73657fc380..56f062b562 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Drawing as WorksheetDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; @@ -132,18 +133,23 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal $extension = ''; $mimeType = ''; - if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) { - $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getExtension()); - $mimeType = $this->getImageMimeType($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getPath()); - } elseif ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { - $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType()); + $drawing = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i); + if ($drawing instanceof WorksheetDrawing && $drawing->getPath() !== '') { + $extension = strtolower($drawing->getExtension()); + if ($drawing->getIsUrl()) { + $mimeType = image_type_to_mime_type($drawing->getType()); + } else { + $mimeType = $this->getImageMimeType($drawing->getPath()); + } + } elseif ($drawing instanceof MemoryDrawing) { + $extension = strtolower($drawing->getMimeType()); $extension = explode('/', $extension); $extension = $extension[1]; - $mimeType = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType(); + $mimeType = $drawing->getMimeType(); } - if (!isset($aMediaContentTypes[$extension])) { + if ($mimeType !== '' && !isset($aMediaContentTypes[$extension])) { $aMediaContentTypes[$extension] = $mimeType; $this->writeDefaultContentType($objWriter, $extension, $mimeType); @@ -162,7 +168,7 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal for ($i = 0; $i < $sheetCount; ++$i) { if (count($spreadsheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { foreach ($spreadsheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { - if (!isset($aMediaContentTypes[strtolower($image->getExtension())])) { + if ($image->getPath() !== '' && !isset($aMediaContentTypes[strtolower($image->getExtension())])) { $aMediaContentTypes[strtolower($image->getExtension())] = $this->getImageMimeType($image->getPath()); $this->writeDefaultContentType($objWriter, strtolower($image->getExtension()), $aMediaContentTypes[strtolower($image->getExtension())]); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php index e7f9901013..bf86eb69c0 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheetTests\Reader\Utility\File; @@ -9,7 +10,8 @@ class URLImageTest extends TestCase { - public function testURLImageSource(): void + // https://github.com/readthedocs/readthedocs.org/issues/11615 + public function xtestURLImageSource(): void { if (getenv('SKIP_URL_IMAGE_TEST') === '1') { self::markTestSkipped('Skipped due to setting of environment variable'); @@ -39,4 +41,28 @@ public function testURLImageSource(): void self::assertSame('png', $extension); } } + + public function xtestURLImageSourceNotFound(): void + { + if (getenv('SKIP_URL_IMAGE_TEST') === '1') { + self::markTestSkipped('Skipped due to setting of environment variable'); + } + $filename = realpath(__DIR__ . '/../../../data/Reader/XLSX/urlImage.notfound.xlsx'); + self::assertNotFalse($filename); + $reader = IOFactory::createReader('Xlsx'); + $spreadsheet = $reader->load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + $collection = $worksheet->getDrawingCollection(); + self::assertCount(0, $collection); + } + + public function testURLImageSourceBadProtocol(): void + { + $filename = realpath(__DIR__ . '/../../../data/Reader/XLSX/urlImage.bad.dontuse'); + self::assertNotFalse($filename); + $this->expectException(SpreadsheetException::class); + $this->expectExceptionMessage('Invalid protocol for linked drawing'); + $reader = IOFactory::createReader('Xlsx'); + $reader->load($filename); + } } diff --git a/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php b/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php index e3d23230c1..f2db5c5904 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Html; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Writer\Html; @@ -57,7 +58,9 @@ public function testSheetWithImageBelowData(): void // Add a drawing to the worksheet $drawing = new Drawing(); - $drawing->setPath('foo.png', false); + $path = 'tests/data/Writer/XLSX/blue_square.png'; + $drawing->setPath($path); + self::assertSame($path, $drawing->getPath()); $drawing->setCoordinates('A5'); $drawing->setWorksheet($sheet); @@ -72,13 +75,51 @@ public function testSheetWithImageRightOfData(): void // Add a drawing to the worksheet $drawing = new Drawing(); - $drawing->setPath('foo.png', false); + $path = 'tests/data/Writer/XLSX/blue_square.png'; + $drawing->setPath($path); + self::assertSame($path, $drawing->getPath()); $drawing->setCoordinates('E1'); $drawing->setWorksheet($sheet); $this->assertMaxColumnAndMaxRow($spreadsheet, 5, 3); } + public function testSheetWithBadImageRightOfData(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('B3', 'foo'); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $path = 'tests/data/Writer/XLSX/xblue_square.png'; + $drawing->setPath($path, false); + self::assertSame('', $drawing->getPath()); + $drawing->setCoordinates('E1'); + $drawing->setWorksheet($sheet); + + $this->assertMaxColumnAndMaxRow($spreadsheet, 2, 3); + } + + public function testSheetWithBadImageRightOfDataThrow(): void + { + $this->expectException(PhpSpreadsheetException::class); + $this->expectExceptionMessage('not found!'); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('B3', 'foo'); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $path = 'tests/data/Writer/XLSX/xblue_square.png'; + $drawing->setPath($path); + self::assertSame('', $drawing->getPath()); + $drawing->setCoordinates('E1'); + $drawing->setWorksheet($sheet); + + $this->assertMaxColumnAndMaxRow($spreadsheet, 2, 3); + } + private function assertMaxColumnAndMaxRow(Spreadsheet $spreadsheet, int $expectedColumnCount, int $expectedRowCount): void { $writer = new Html($spreadsheet); diff --git a/tests/PhpSpreadsheetTests/Writer/Html/ImageEmbedTest.php b/tests/PhpSpreadsheetTests/Writer/Html/ImageEmbedTest.php new file mode 100644 index 0000000000..bc9e40ed4c --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/ImageEmbedTest.php @@ -0,0 +1,45 @@ +getActiveSheet(); + + $drawing = new Drawing(); + $drawing->setName('Not an image'); + $drawing->setDescription('Non-image'); + $drawing->setPath(__FILE__, false); + $drawing->setCoordinates('A1'); + $drawing->setCoordinates2('E4'); + $drawing->setWorksheet($sheet); + + $drawing = new Drawing(); + $drawing->setName('Blue Square'); + $drawing->setPath('tests/data/Writer/XLSX/blue_square.png'); + $drawing->setCoordinates('A5'); + $drawing->setCoordinates2('E8'); + $drawing->setWorksheet($sheet); + + $writer = new HtmlWriter($spreadsheet); + $writer->setEmbedImages(true); + $html = $writer->generateHTMLAll(); + self::assertSame(1, substr_count($html, 'disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/urlImage.bad.dontuse b/tests/data/Reader/XLSX/urlImage.bad.dontuse new file mode 100644 index 0000000000000000000000000000000000000000..3e39a809ff7306eec8c25b798682d30184f7b971 GIT binary patch literal 10114 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 SWdHyO|2V+ua6|q5?*9OYvxM*f literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/urlImage.notfound.xlsx b/tests/data/Reader/XLSX/urlImage.notfound.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e335a80de1cf752f325514998efacfb743625fe4 GIT binary patch literal 10116 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 RG5~-M``E$Y0Qm9p{tt*_h93X` literal 0 HcmV?d00001