
Willst du, dass ich mir die ganze Datei jetzt komplett durchsehe und dir eine Sicherheits- und Funktionsbewertung Abschnitt für Abschnitt schreibe?
Du:
ja bitte
ChatGPT:
Nachgedacht für 2m 57s
Alles klar—ich hab deine send.php komplett durchleuchtet. Kurzes Fazit vorweg: sehr solide Basis mit CSRF-Token, Honeypot, Zeitbremse, Captcha, sauberem PHPMailer-Setup (kein User-spoofing bei From), Retry-Mechanismus und ordentlichen HTML/Text-Bodies. Für Produktion gibt’s aber ein paar sinnvolle Härtungen und kleine Stolpersteine.
Was passt
CSRF: Prüfung gegen Session-Token und Abbruch mit 400 – korrekt.
Anti-Spam: Honeypot + Mindestzeit (≥ 3 s) + Captcha inkl. Regeneration bei Fehlversuch – gut.
PHPMailer:
Bei SMTP wird setFrom($smtp_user, …) genutzt → kein User-From-Spoofing.
Ohne SMTP fallback via isMail() – ok als Fallback.
Input-Sanitizing: sanitize_input() (trim/stripslashes/htmlspecialchars) und zusätzlich FILTER_VALIDATE_EMAIL bei E-Mailfeldern.
Attachments: Einzigartige Dateinamen via uniqid(), Größenlimit, erlaubte Endungen, addAttachment() – funktioniert.
Antwort-Codes & JSON: Konsequentes http_response_code(...) + json_encode(...).
Auto-Reply (optional): sauber gekapselt, nur bei gültiger Absenderadresse.
Was du verbessern solltest (Security/Robustheit)
JSON Content-Type setzen
Aktuell fehlt ein expliziter Header (Browser/JS freut sich darüber).
header('Content-Type: application/json; charset=utf-8');
→ direkt am Anfang der POST-Verarbeitung, bevor echo json_encode(...) aufgerufen wird.
MIME-Type prüfen (nicht nur Dateiendung)
Du prüfst die Endung, aber nicht den wirklichen Typ. Ergänze finfo:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowed_mimes = [
'image/jpeg', 'image/png',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
if (!in_array($mime, $allowed_mimes, true)) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Ungültiger Dateityp."]);
exit;
}
(Optional: zusätzlich Magic-Bytes prüfen.)
Uploads-Verzeichnis absichern
Lege außerhalb des Webroots ab, z. B. ../data/uploads/ (und passe $uploads_dir an).
Wenn im Webroot, dann wenigstens .htaccess mit Deny from all oder Nginx-Location-Block.
Prüfe/erzeuge Ordner bei Bedarf:
if (!is_dir($uploads_dir)) { mkdir($uploads_dir, 0750, true); }
Maximale Anzahl Attachments begrenzen
Verhindert Ressourcenmissbrauch:
$max_files = 3;
$count_ok = 0;
foreach ($_FILES as $file) {
if ($file['error'] === UPLOAD_ERR_OK) {
$count_ok++;
if ($count_ok > $max_files) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Zu viele Dateien."]);
exit;
}
}
}
Hochgeladene Dateien nach Versand aufräumen (falls nicht benötigt)
Minimiert Risiko & Speicherverbrauch:
// nach $mail->send():
foreach ($uploaded_files as $file) {
if (is_file($file['path'])) { @unlink($file['path']); }
}
SMTP TLS-Validierung nicht global abschalten
In SMTPOptions steht aktuell:
"verify_peer" => false,
"verify_peer_name" => false,
"allow_self_signed" => true
→ Das bitte nur testweise. In Produktion entfernen oder auf true setzen und gültige CA nutzen.
From im Fallback verbessern
Im isMail()-Zweig setzt du setFrom($recipient_email, $sender_name). Besser: eine interne Adresse deiner Domain, z. B.
no-reply@deinedomain.tld, um DMARC/SPF-Probleme zu vermeiden:
$mail->setFrom('
no-reply@deinedomain.tld', $sender_name);
$mail->addAddress($recipient_email);
if ($sender_email) { $mail->addReplyTo($sender_email); }
Rate-Limiting pro IP / Session
Einfache Bremse gegen Spam-Wellen:
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$_SESSION['contact_attempts'] = $_SESSION['contact_attempts'] ?? [];
$_SESSION['contact_attempts'] = array_filter(
$_SESSION['contact_attempts'],
fn($ts) => $ts > time() - 60 // nur letzte 60s
);
if (count($_SESSION['contact_attempts']) >= 5) {
http_response_code(429);
echo json_encode(["success" => false, "message" => "Zu viele Anfragen. Bitte später erneut versuchen."]);
exit;
}
$_SESSION['contact_attempts'][] = time();
Pflichtfelder-Liste synchron halten
Du nutzt:
$required_fields = ["name", "email", "Betreff", "Nachricht", "Datenschutz"];
→ Achte auf exakte Feldnamen/Schreibweise im Frontend (Case-Sensitivity). Tipp: zentral konfigurieren oder aus dem Formular generieren.
„Kopie an Absender“ implementieren (optional)
Du liest send_copy_to_sender, aber ich sehe keinen separaten Versand als Kopie. Falls gewünscht:
if ($send_copy_to_sender && $sender_email) {
try {
$copy = clone $mail;
$copy->clearAllRecipients();
$copy->addAddress($sender_email);
$copy->Subject = "[Kopie] " . $subject;
$copy->send();
} catch (Exception $e) {
error_log("Copy send failed: " . $e->getMessage());
}
}
HTML-Escaping konsequent
In den HTML-Tabellen nutzt du bereits htmlspecialchars()/nl2br(). Behalte das für alle Werte/Labels bei (auch bei Arrays via array_map('htmlspecialchars', ...)). Scheint überall korrekt, nur „auf Lücken achten“.
Fehlertexte / Leaks
Du loggst Ausnahmen via error_log() und gibst dem Nutzer generische Messages – top. Achte darauf, niemals SMTP-User/Pass im Klartext auszugeben (tust du nicht).
CSRF/Captcha-Lebenszyklus
Das Token wird geprüft – die Erzeugung passiert vermutlich in der Formularseite. Gute Praxis: Token pro Render neu generieren (One-time-Token). In send.php ist das ok, nur Hinweis für die Gegenstelle. Captcha wird bei Fehler neu generiert – super.
Mini-Diffs (direkt einbaubar)
(a) JSON Header & MIME-Check & Ordner)
// Nach session_start();
header('Content-Type: application/json; charset=utf-8');
// Vor Upload-Verarbeitung:
if (!is_dir($uploads_dir)) { mkdir($uploads_dir, 0750, true); }
$finfo = finfo_open(FILEINFO_MIME_TYPE);
...
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowed_mimes, true)) { ... }
(b) Nach Versand aufräumen)
if ($sent) {
foreach ($uploaded_files as $file) {
if (is_file($file['path'])) { @unlink($file['path']); }
}
}
(c) SMTPOptions strenger)
// Entfernen oder hart absichern:
$mail->SMTPOptions = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
],
];
(d) Fallback-From verbessern)
$mail->isMail();
$mail->setFrom('
no-reply@deinedomain.tld', $sender_name);
Wenn du willst, setze ich dir die Änderungen gern zielgenau in deiner Datei (mit sauber kommentierten Blöcken), oder ich gebe dir eine bereinigte Variante deiner send.php mit allen oben genannten Härtungen direkt fertig aus.