Steuerkram/tests/67_BelegXmlXsltXssTest.php
fds2610-bot 3e5810990c Fix #217: Content-Disposition-Header gegen Parameter-Injection maskieren
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-06 15:20:21 +02:00

89 lines
3.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
/**
* Test 67: Stored XSS über XSLT in hochgeladenen XML-Belegen ist geschlossen
* (BelegController::serveFile liefert XML-Belege als Download statt inline aus).
*/
class BelegXmlXsltXssTest extends TestCase
{
protected string $testName = 'Test 67: XML-Belege werden als Attachment statt inline ausgeliefert';
public function run(): void
{
$belegNr = \Steuerkram\Models\Beleg::generateBelegNr();
$year = date('Y');
$relDir = "{$year}/{$belegNr}";
$absDir = $this->makeTempDir(\Steuerkram\Config::get('upload_dir') . "/{$relDir}");
$storagePath = "{$relDir}/original.xml";
$absPath = \Steuerkram\Config::get('upload_dir') . "/{$storagePath}";
$xmlPayload = <<<'XML'
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
<doc>
<xsl:stylesheet id="stylesheet" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html><body><script>alert(document.domain)</script></body></html>
</xsl:template>
</xsl:stylesheet>
</doc>
XML;
file_put_contents($absPath, $xmlPayload);
$belegId = \Steuerkram\Models\Beleg::create([
'beleg_nr' => $belegNr,
'filename' => 'rechnung.xml',
'storage_path' => $storagePath,
'mime_type' => 'text/xml',
'file_hash' => hash('sha256', $xmlPayload),
'beleg_datum' => date('Y-m-d'),
'rechnungs_nr' => null,
'betrag_brutto' => 100.00,
'belegtyp' => 'Eingangsrechnung',
'notes' => 'XSLT-XSS-Test',
]);
$controller = new \Steuerkram\Controllers\BelegController();
ob_start();
$controller->serveFile(['id' => (string)$belegId]);
$body = (string)ob_get_clean();
// Der Beleg muss überhaupt ausgeliefert werden (kein 403 durch den
// Pfad-Check, kein 404) – sonst sagt die Disposition-Prüfung nichts aus.
$this->assert(
str_contains($body, 'xsl:stylesheet'),
'serveFile() liefert den XML-Beleg aus (kein 403/404)'
);
// headers_list() ist unter der CLI-SAPI immer leer, der real gesetzte
// Header lässt sich hier also nicht auslesen. Geprüft wird deshalb die
// Auslieferungslogik in serveFile() selbst.
$controllerFile = __DIR__ . '/../src/Controllers/BelegController.php';
$quelltext = (string)file_get_contents($controllerFile);
$start = strpos($quelltext, 'function serveFile');
$this->assert($start !== false, 'Methode serveFile() ist im BelegController vorhanden');
$rest = substr($quelltext, (int)$start);
$laenge = preg_match('/\n (?:public|private|protected) function /', $rest, $treffer, PREG_OFFSET_CAPTURE)
? $treffer[0][1]
: strlen($rest);
$serveFile = substr($rest, 0, $laenge);
$this->assert(
str_contains($serveFile, "'application/xml'") && str_contains($serveFile, "'text/xml'"),
'serveFile() erkennt XML-MIME-Typen gesondert'
);
$this->assert(
(bool)preg_match("/\?\s*'attachment'\s*:\s*'inline'/", $serveFile),
'XML wird als attachment ausgeliefert, alles andere weiterhin inline'
);
$this->assert(
(bool)preg_match("/contentDispositionHeader\(\s*\\\$disposition\s*,/", $serveFile),
'Der Content-Disposition-Header nutzt die ermittelte Disposition (nicht fest inline)'
);
}
}