diff --git a/Changes.md b/Changes.md index 74f4cc1..0ae2aa4 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,16 @@ --- +## 2026-07-08 – Fix: SMTP-/E-Mail-Header-Injection über ungeprüfte Empfänger-/CC-Adressen (Issue #117) + +- **Kritisch:** Empfänger- und CC-Adressen (Rechnungsversand, Test-Mail, Kundenstammdaten) wurden nur `trim()`t, aber nie auf gültiges Format oder enthaltene `\r`/`\n` geprüft. Da `SmtpMailer` das SMTP-Protokoll über einen rohen Socket implementiert, konnte eine Adresse mit eingebettetem CRLF zusätzliche `RCPT TO:`-Kommandos oder Mail-Header einschleusen (CWE-93, SMTP-/Header-Injection). +- **`src/Services/SmtpMailer.php`:** `send()` validiert jetzt jede Empfängeradresse mit `filter_var(..., FILTER_VALIDATE_EMAIL)`, bevor überhaupt eine SMTP-Verbindung aufgebaut wird; ungültige Adressen (inkl. CRLF-Payloads) werfen eine `RuntimeException`. Da alle Versandwege (`EmailService::sendInvoice()`, `sendTestMail()`) durch diese einzige Methode laufen, ist die Prüfung zentral wirksam. +- **`src/Controllers/CustomerController.php`:** Formularvalidierung prüft `email`, `email_cc1`, `email_cc2` zusätzlich mit `filter_var(FILTER_VALIDATE_EMAIL)`, damit gar nicht erst ungültige Adressen in der Kundendatenbank landen. +- **`src/Views/customers/form.php`:** Fehleranzeige für die drei E-Mail-Felder ergänzt. +- **`tests/50_EmailHeaderInjectionTest.php`:** Neuer Test prüft, dass CRLF-Injection-Payloads und ungültige Adressformate von `SmtpMailer::send()` abgelehnt werden. + +--- + ## 2026-07-08 – Fix: Unauthentifizierter Direktzugriff auf Belege/Rechnungs-PDFs (Issue #116) - **Kritisch:** Hochgeladene Belege und Rechnungs-PDFs lagen unter `public/static/uploads/...` im Web-Root und wurden von Apache direkt ausgeliefert, bevor der Request `index.php`/`Auth::require()` erreichte – ein Angreifer konnte Belege ohne Anmeldung per URL abrufen. diff --git a/src/Controllers/CustomerController.php b/src/Controllers/CustomerController.php index 1399ec1..af0cb51 100644 --- a/src/Controllers/CustomerController.php +++ b/src/Controllers/CustomerController.php @@ -139,6 +139,14 @@ class CustomerController extends BaseController if (empty($data['strasse'])) $errors['strasse'] = 'Pflichtfeld'; if (empty($data['plz'])) $errors['plz'] = 'Pflichtfeld'; if (empty($data['ort'])) $errors['ort'] = 'Pflichtfeld'; + + foreach (['email', 'email_cc1', 'email_cc2'] as $field) { + $value = trim($data[$field] ?? ''); + if ($value !== '' && filter_var($value, FILTER_VALIDATE_EMAIL) === false) { + $errors[$field] = 'Ungültige E-Mail-Adresse'; + } + } + return $errors; } } diff --git a/src/Services/SmtpMailer.php b/src/Services/SmtpMailer.php index 494b136..b54b493 100644 --- a/src/Services/SmtpMailer.php +++ b/src/Services/SmtpMailer.php @@ -39,6 +39,12 @@ class SmtpMailer ?string $attachContent = null, ?string $attachName = null, ): void { + foreach ($to as $addr) { + if (filter_var($addr, FILTER_VALIDATE_EMAIL) === false) { + throw new \RuntimeException('Ungültige E-Mail-Adresse: ' . $addr); + } + } + $this->connect(); $this->authenticate(); $this->sendMessage($to, $subject, $body, $attachContent, $attachName); diff --git a/src/Views/customers/form.php b/src/Views/customers/form.php index 1a860f3..c14aa4b 100644 --- a/src/Views/customers/form.php +++ b/src/Views/customers/form.php @@ -52,7 +52,9 @@
diff --git a/tests/50_EmailHeaderInjectionTest.php b/tests/50_EmailHeaderInjectionTest.php
new file mode 100644
index 0000000..80c4329
--- /dev/null
+++ b/tests/50_EmailHeaderInjectionTest.php
@@ -0,0 +1,74 @@
+send(
+ ["opfer@example.com\r\nRCPT TO: