diff --git a/Changes.md b/Changes.md index 4147f3e..111093e 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,13 @@ --- +## 2026-07-27 – Fix: Stored XSS über XSLT in hochgeladenen XML-Belegen (Issue #135) + +- `BelegController::serveFile()` liefert Belege mit dem in der DB hinterlegten `mime_type` und `Content-Disposition: inline` unverändert an den Browser aus. Da für Belege `application/xml`/`text/xml` als MIME-Typ erlaubt ist, konnte eine hochgeladene XML-Datei mit eingebettetem ``/`xsl:stylesheet` beim Öffnen über den „Datei öffnen“-Link (`target="_blank"`, kein `download`-Attribut) direkt im Browser (v.a. Firefox) per XSLT-Transformation zu ausführbarem ` + + + + 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]); + ob_get_clean(); + + $headers = headers_list(); + $disposition = ''; + foreach ($headers as $header) { + if (stripos($header, 'Content-Disposition:') === 0) { + $disposition = $header; + } + } + + $this->assertNotEmpty( + $disposition, + 'Es wurde ein Content-Disposition-Header gesetzt' + ); + $this->assert( + stripos($disposition, 'attachment') !== false, + "XML-Beleg wird als attachment statt inline ausgeliefert (erhalten: {$disposition})" + ); + + @unlink($absPath); + } +}