From 5110915de2ab05db1ba5b4d3237f5c14b72bcaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 14 Jul 2026 18:14:59 +0200 Subject: [PATCH] Prevent formula injection into XLSX export --- .../ImportExportSystem/EntityExporter.php | 9 ++++++-- .../ImportExportSystem/EntityExporterTest.php | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Services/ImportExportSystem/EntityExporter.php b/src/Services/ImportExportSystem/EntityExporter.php index ab87a905..4cd92cc4 100644 --- a/src/Services/ImportExportSystem/EntityExporter.php +++ b/src/Services/ImportExportSystem/EntityExporter.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Services\ImportExportSystem; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Cell\DataType; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Helpers\FilenameSanatizer; @@ -179,7 +180,11 @@ class EntityExporter foreach ($columns as $column) { $cellCoordinate = Coordinate::stringFromColumnIndex($colIndex) . $rowIndex; - $worksheet->setCellValue($cellCoordinate, $column); + if (is_numeric(trim($column, '"'))) { // Check if the column value is numeric after trimming quotes, as values are surrounded by quotes in CSV + $worksheet->setCellValueExplicit($cellCoordinate, $column, DataType::TYPE_NUMERIC); + } else { + $worksheet->setCellValueExplicit($cellCoordinate, $column, DataType::TYPE_STRING); + } $colIndex++; } $rowIndex++; @@ -268,7 +273,7 @@ class EntityExporter //Remove percent for fallback $fallback = str_replace("%", "_", $filename); - + // Create the disposition of the file $disposition = $response->headers->makeDisposition( ResponseHeaderBag::DISPOSITION_ATTACHMENT, diff --git a/tests/Services/ImportExportSystem/EntityExporterTest.php b/tests/Services/ImportExportSystem/EntityExporterTest.php index e4518961..2c400102 100644 --- a/tests/Services/ImportExportSystem/EntityExporterTest.php +++ b/tests/Services/ImportExportSystem/EntityExporterTest.php @@ -101,6 +101,28 @@ final class EntityExporterTest extends WebTestCase unlink($tempFile); } + public function testExportExcelFormulaInjectionPrevention(): void + { + // Values starting with formula characters must be stored as plain strings, not evaluated formulas + $entity = (new Category())->setName('=1+1')->setComment('@SUM(A1)'); + + $xlsxData = $this->service->exportEntities([$entity], ['format' => 'xlsx', 'level' => 'simple']); + $this->assertNotEmpty($xlsxData); + + $tempFile = tempnam(sys_get_temp_dir(), 'test_formula') . '.xlsx'; + file_put_contents($tempFile, $xlsxData); + + $spreadsheet = IOFactory::load($tempFile); + $worksheet = $spreadsheet->getActiveSheet(); + + // The formula-prefixed name must be stored as a plain string, not a formula + $cell = $worksheet->getCell('A2'); + $this->assertSame(\PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING, $cell->getDataType()); + $this->assertSame('=1+1', $cell->getValue()); + + unlink($tempFile); + } + public function testExportExcelFromRequest(): void { $entities = $this->getEntities();