Steuerkram/tests/55_MailSubjectHeaderInjectionTest.php

67 lines
2.7 KiB
PHP

<?php
declare(strict_types=1);
use Steuerkram\Models\Customer;
use Steuerkram\Services\SmtpMailer;
class MailSubjectHeaderInjectionTest extends TestCase
{
protected string $testName = 'Test 55: Header-Injection über mail_subject_template verhindert';
public function run(): void
{
// CRLF-Payload in der Betreff-Vorlage wird beim Speichern entfernt
$id = Customer::create([
'nachname' => 'Header-Injection-Test',
'strasse' => 'Musterweg 1',
'plz' => '12345',
'ort' => 'Musterstadt',
'mail_subject_template' => "Rechnung %RNR%\r\nBcc: angreifer@example.com",
]);
$this->assertGreaterThan(0, $id, 'Kunde angelegt');
$customer = Customer::find($id);
$this->assert(
!str_contains($customer['mail_subject_template'], "\r") && !str_contains($customer['mail_subject_template'], "\n"),
'Gespeicherte Betreff-Vorlage enthält kein CR/LF mehr'
);
$this->assert(
str_contains($customer['mail_subject_template'], 'Bcc: angreifer@example.com'),
'Injizierter Text bleibt als Klartext im Betreff, wird aber nicht mehr als Header interpretierbar'
);
// Update mit LF-only-Payload wird ebenfalls bereinigt
Customer::update($id, [
'nachname' => 'Header-Injection-Test',
'strasse' => 'Musterweg 1',
'plz' => '12345',
'ort' => 'Musterstadt',
'mail_subject_template' => "Rechnung %RNR%\nBcc: angreifer2@example.com",
]);
$customer = Customer::find($id);
$this->assert(
!str_contains($customer['mail_subject_template'], "\n"),
'Betreff-Vorlage nach Update ebenfalls ohne LF gespeichert'
);
// Defense-in-depth: SmtpMailer::encodeHeader() entfernt CRLF selbst, falls ein
// CRLF-Betreff trotzdem irgendwo in den Mailer gelangt (Reflection auf private Methode)
$mailer = new SmtpMailer(
host: '127.0.0.1',
port: 1,
encryption: 'none',
user: '',
pass: '',
fromEmail: 'absender@example.com',
fromName: 'Absender',
);
$ref = new \ReflectionMethod($mailer, 'encodeHeader');
$ref->setAccessible(true);
$result = $ref->invoke($mailer, "Rechnung 2026-1\r\nBcc: angreifer@example.com");
$this->assert(
!str_contains($result, "\r") && !str_contains($result, "\n"),
'encodeHeader() entfernt CR/LF aus Header-Werten'
);
}
}