From 65f9bc7d81b7db44e69ab261058ac3d9632be694 Mon Sep 17 00:00:00 2001 From: pw Date: Thu, 26 Jun 2025 19:17:28 +0200 Subject: [PATCH] process_email.php aktualisiert --- process_email.php | 218 +++++++++++++++++++++++++++++++++------------- 1 file changed, 158 insertions(+), 60 deletions(-) diff --git a/process_email.php b/process_email.php index f405c77..bebf0d4 100644 --- a/process_email.php +++ b/process_email.php @@ -8,16 +8,33 @@ error_reporting(E_ALL); ini_set('memory_limit', '256M'); set_time_limit(120); +// Setze die interne Zeichenkodierung auf UTF-8 für alle Multi-Byte-String-Funktionen +// Dies ist entscheidend für die korrekte Verarbeitung von Emojis und Sonderzeichen. +mb_internal_encoding("UTF-8"); +mb_regex_encoding("UTF-8"); // Auch für Regex-Funktionen + function parseEmailHeaders($emailContent) { $headers = []; + // Stellen Sie sicher, dass $emailContent ein String ist, bevor explode aufgerufen wird. + if (!is_string($emailContent)) { + return $headers; + } + $lines = explode("\n", $emailContent); $inHeaders = true; $currentHeader = ''; + $headerValueBuffer = ''; // Puffer für mehrzeilige Header-Werte foreach ($lines as $line) { $line = rtrim($line, "\r"); // Entferne CR am Ende + $line = trim($line); // Trimmt Leerzeichen am Anfang und Ende + if (empty($line)) { - $inHeaders = false; // Leere Zeile bedeutet Ende der Header + // Leere Zeile bedeutet Ende der Header. Verarbeite den letzten Header im Puffer. + if ($currentHeader !== '' && $headerValueBuffer !== '') { + $headers[$currentHeader] = trim($headerValueBuffer); + } + $inHeaders = false; continue; } @@ -25,22 +42,29 @@ function parseEmailHeaders($emailContent) { // Wenn die Zeile mit Leerzeichen oder Tab beginnt, ist es eine Fortsetzung des vorherigen Headers if (preg_match('/^\s/', $line)) { if ($currentHeader !== '') { - $headers[$currentHeader] .= ' ' . trim($line); + $headerValueBuffer .= ' ' . $line; // Füge zur Puffer hinzu } } else { - // Neuer Header - if (preg_match('/^([^:]+):(.*)$/', $line, $matches)) { + // Neuer Header: Verarbeite zuerst den vorherigen Header, falls vorhanden + if ($currentHeader !== '' && $headerValueBuffer !== '') { + $headers[$currentHeader] = trim($headerValueBuffer); + } + + // Parsen des neuen Headers + if (preg_match('/^([^:]+):(.*)$/U', $line, $matches)) { // /U für ungreedy $headerName = trim($matches[1]); - $headerValue = trim($matches[2]); - $headers[$headerName] = $headerValue; + $headerValueBuffer = trim($matches[2]); // Initialisiere Puffer mit neuem Wert + $headers[$headerName] = $headerValueBuffer; // vorläufig speichern $currentHeader = $headerName; } else { - // Zeile sieht nicht nach einem Header aus, aber wir sind noch in den Headern - // Das könnte ein Problem mit der E-Mail-Formatierung sein. - // Wir versuchen, es dem letzten Header zuzuweisen oder ignorieren es. - if ($currentHeader !== '') { - $headers[$currentHeader] .= "\n" . $line; // Anfügen als neue Zeile im Wert - } + // Zeile sieht nicht nach einem Header aus, könnte aber Teil des Bodies sein, + // der fälschlicherweise in den Header-Bereich gerutscht ist. + // Oder ein fehlerhafter Header. + // Ignoriere die Zeile oder hänge sie an den letzten Header an, + // aber sei vorsichtig, um unendliche Schleifen oder Speicherprobleme zu vermeiden. + // Hier wird sie ignoriert, um Robustheit zu gewährleisten. + $currentHeader = ''; // Setze aktuellen Header zurück, um weitere Anhängungen zu verhindern + $headerValueBuffer = ''; } } } else { @@ -48,6 +72,10 @@ function parseEmailHeaders($emailContent) { break; } } + // Nach der Schleife den letzten gesammelten Header hinzufügen, falls vorhanden + if ($currentHeader !== '' && $headerValueBuffer !== '' && !isset($headers[$currentHeader])) { + $headers[$currentHeader] = trim($headerValueBuffer); + } return $headers; } @@ -55,17 +83,27 @@ function checkPhishing($headers, $emailContent) { $warnings = []; $phishingScore = 0; // Ein einfacher Zähler für Phishing-Indikatoren + // --- Sicherstellen, dass Headers und Content als Strings behandelt werden --- + $subject = (string)($headers['Subject'] ?? ''); + $from = (string)($headers['From'] ?? ''); + $returnPath = (string)($headers['Return-Path'] ?? ''); + $replyTo = (string)($headers['Reply-To'] ?? ''); + $xMailer = (string)($headers['X-Mailer'] ?? ''); + $userAgent = (string)($headers['User-Agent'] ?? ''); + $authResults = (string)($headers['Authentication-Results'] ?? ''); + $emailContent = (string)$emailContent; // Sicherstellen, dass $emailContent ein String ist + // --- Prüfung des E-Mail-Inhalts (Body und Betreff) - Priorität 1 --- // 1. Häufige Phishing-Keywords im Betreff oder Body $keywords = ['bestätigung', 'konto', 'passwort', 'sicherheit', 'aktualisieren', 'blockiert', 'dringend', 'zahlung', 'rechnung', 'gewinn', 'glückwunsch', 'probleme', 'verifizierung', 'warnung']; $foundContentKeywords = 0; foreach ($keywords as $keyword) { - if (stripos($headers['Subject'] ?? '', $keyword) !== false) { + if (mb_stripos($subject, $keyword) !== false) { // mb_stripos für Multi-Byte-Zeichen $warnings[] = "Hinweis (Inhalt): Das Wort '" . htmlspecialchars($keyword) . "' wurde im Betreff gefunden. Dies ist oft in Phishing-Mails zu finden."; $foundContentKeywords++; } - if (stripos($emailContent, $keyword) !== false) { + if (mb_stripos($emailContent, $keyword) !== false) { // mb_stripos für Multi-Byte-Zeichen $warnings[] = "Hinweis (Inhalt): Das Wort '" . htmlspecialchars($keyword) . "' wurde im E-Mail-Text gefunden. Dies ist oft in Phishing-Mails zu finden."; $foundContentKeywords++; } @@ -73,25 +111,21 @@ function checkPhishing($headers, $emailContent) { if ($foundContentKeywords > 0) $phishingScore += min($foundContentKeywords, 3); // Max 3 Punkte für Keywords // 2. Ungewöhnliche Zeichen oder Encoding im Betreff (könnte auf Verschleierung hindeuten) - $subject = isset($headers['Subject']) ? $headers['Subject'] : ''; - if (preg_match('/=\?UTF-8\?B\?/', $subject) || preg_match('/=\?UTF-8\?Q\?/', $subject)) { - // Dies ist oft legitimes Encoding, aber bei Phishing wird es manchmal missbraucht. - // Keine direkte Warnung hier, da es zu viele False-Positives gäbe, aber ein leichter Punkt. - // $warnings[] = "Hinweis (Inhalt): Der Betreff enthält codierte Zeichen (Quoted-Printable oder Base64)."; - // $phishingScore += 0.5; - } + // Diese Prüfung wird jetzt weniger stark gewichtet, da mb_internal_encoding hilft. + // Falls immer noch Probleme auftreten, kann man hier spezifischer nach fehlerhaften Encodings suchen. // 3. Links prüfen (verbessert: prüft auf offensichtliche URL-Diskrepanzen, IPs, Shortener) - $from = isset($headers['From']) ? strtolower($headers['From']) : ''; - preg_match('/@([^>]+)/', $from, $fromDomainMatch); - $fromDomain = isset($fromDomainMatch[1]) ? trim($fromDomainMatch[1]) : ''; + $fromDomain = ''; + if (preg_match('/@([^>]+)/', $from, $fromDomainMatch)) { + $fromDomain = trim($fromDomainMatch[1]); + } $foundSuspiciousLinks = 0; if (preg_match_all('/https?:\/\/[^\s"\']+/i', $emailContent, $matches)) { foreach (array_unique($matches[0]) as $url) { // Einzigartige URLs prüfen $urlParts = parse_url($url); - $urlDomain = isset($urlParts['host']) ? strtolower($urlParts['host']) : ''; + $urlDomain = isset($urlParts['host']) ? mb_strtolower($urlParts['host']) : ''; // mb_strtolower für Multi-Byte-Zeichen // Check for direct IP address in URL if (filter_var($urlDomain, FILTER_VALIDATE_IP)) { @@ -104,7 +138,7 @@ function checkPhishing($headers, $emailContent) { if ($urlDomain && $fromDomain && !empty($fromDomain)) { // Eine robustere Prüfung könnte hier auch Subdomain-Tricks erkennen // z.B. bank.com.malicious.example.com - if (strpos($urlDomain, $fromDomain) === false && strpos($fromDomain, $urlDomain) === false) { + if (mb_strpos($urlDomain, $fromDomain) === false && mb_strpos($fromDomain, $urlDomain) === false) { // mb_strpos für Multi-Byte-Zeichen $warnings[] = "Kritische Warnung (Inhalt/Links): Die Domain im Link ('" . htmlspecialchars($urlDomain) . "') weicht von der Absenderdomain ('" . htmlspecialchars($fromDomain) . "') ab. Vorsicht!"; $phishingScore += 3; $foundSuspiciousLinks++; @@ -113,7 +147,7 @@ function checkPhishing($headers, $emailContent) { // Add check for URL shorteners (simple regex) $shortenerDomains = ['bit.ly', 'goo.gl', 'tinyurl.com', 'ow.ly', 't.co', 'cutt.ly', 'is.gd', 's.id']; // Add more as needed foreach ($shortenerDomains as $shortener) { - if (strpos($urlDomain, $shortener) !== false) { + if (mb_strpos($urlDomain, $shortener) !== false) { // mb_strpos für Multi-Byte-Zeichen $warnings[] = "Hinweis (Inhalt/Links): Ein Link verwendet einen URL-Shortener ('" . htmlspecialchars($url) . "'). Dies kann zur Verschleierung bösartiger Ziele genutzt werden."; $phishingScore += 1; $foundSuspiciousLinks++; @@ -127,15 +161,15 @@ function checkPhishing($headers, $emailContent) { // --- Prüfung der Header-Daten - Priorität 2 --- // 4. Absenderprüfung (From vs. Return-Path vs. Reply-To) - $returnPath = isset($headers['Return-Path']) ? strtolower($headers['Return-Path']) : ''; - $replyTo = isset($headers['Reply-To']) ? strtolower($headers['Reply-To']) : ''; - - preg_match('/@([^>]+)/', $returnPath, $returnPathDomainMatch); - $returnPathDomain = isset($returnPathDomainMatch[1]) ? trim($returnPathDomainMatch[1]) : ''; - - preg_match('/@([^>]+)/', $replyTo, $replyToDomainMatch); - $replyToDomain = isset($replyToDomainMatch[1]) ? trim($replyToDomainMatch[1]) : ''; + $returnPathDomain = ''; + if (preg_match('/@([^>]+)/', $returnPath, $returnPathDomainMatch)) { + $returnPathDomain = trim($returnPathDomainMatch[1]); + } + $replyToDomain = ''; + if (preg_match('/@([^>]+)/', $replyTo, $replyToDomainMatch)) { + $replyToDomain = trim($replyToDomainMatch[1]); + } if ($fromDomain && $returnPathDomain && $fromDomain !== $returnPathDomain) { $warnings[] = "Warnung (Header): Die Absenderdomain ('" . htmlspecialchars($fromDomain) . "') stimmt nicht mit der 'Return-Path'-Domain ('" . htmlspecialchars($returnPathDomain) . "') überein. Dies könnte ein Indikator für Spoofing sein."; @@ -150,41 +184,63 @@ function checkPhishing($headers, $emailContent) { // 5. Fehlende oder seltsame Header $importantHeaders = ['From', 'To', 'Subject', 'Date', 'Message-ID']; foreach ($importantHeaders as $header) { - if (!isset($headers[$header])) { - $warnings[] = "Hinweis (Header): Der wichtige Header '" . htmlspecialchars($header) . "' fehlt."; + if (!isset($headers[$header]) || empty($headers[$header])) { // Prüfe auch auf leere Header + $warnings[] = "Hinweis (Header): Der wichtige Header '" . htmlspecialchars($header) . "' fehlt oder ist leer."; $phishingScore += 0.5; } } // 6. X-Mailer / User-Agent Prüfung - $xMailer = isset($headers['X-Mailer']) ? strtolower($headers['X-Mailer']) : ''; - $userAgent = isset($headers['User-Agent']) ? strtolower($headers['User-Agent']) : ''; - if (empty($xMailer) && empty($userAgent)) { $warnings[] = "Hinweis (Header): 'X-Mailer' oder 'User-Agent' Header fehlen. Dies ist manchmal bei automatisierten oder ungewöhnlichen Mail-Systemen der Fall."; $phishingScore += 0.5; - } elseif ( - (strpos($xMailer, 'microsoft outlook') === false && strpos($xMailer, 'thunderbird') === false && - strpos($xMailer, 'mail.app') === false && strpos($xMailer, 'gmail') === false && !empty($xMailer)) || - (strpos($userAgent, 'microsoft outlook') === false && strpos($userAgent, 'thunderbird') === false && - strpos($userAgent, 'mail.app') === false && strpos($userAgent, 'gmail') === false && !empty($userAgent)) - ) { - $warnings[] = "Hinweis (Header): Ungewöhnlicher oder unbekannter E-Mail-Client ('" . htmlspecialchars($xMailer . $userAgent) . "') im 'X-Mailer'/'User-Agent' Header erkannt. Kann ein Indikator für Massenversand oder Phishing sein."; - $phishingScore += 1; + } else { + $foundUnusualMailer = false; + $commonMailers = ['microsoft outlook', 'thunderbird', 'mail.app', 'gmail']; + + if (!empty($xMailer)) { + $isCommon = false; + foreach($commonMailers as $mailer) { + if (mb_strpos($xMailer, $mailer) !== false) { + $isCommon = true; + break; + } + } + if (!$isCommon) { + $foundUnusualMailer = true; + } + } + + if (!$foundUnusualMailer && !empty($userAgent)) { + $isCommon = false; + foreach($commonMailers as $mailer) { + if (mb_strpos($userAgent, $mailer) !== false) { + $isCommon = true; + break; + } + } + if (!$isCommon) { + $foundUnusualMailer = true; + } + } + + if ($foundUnusualMailer) { + $warnings[] = "Hinweis (Header): Ungewöhnlicher oder unbekannter E-Mail-Client im 'X-Mailer'/'User-Agent' Header erkannt ('" . htmlspecialchars($xMailer . $userAgent) . "'). Kann ein Indikator für Massenversand oder Phishing sein."; + $phishingScore += 1; + } } // 7. Prüfung von Authentication-Results Header (SPF, DKIM, DMARC) - $authResults = isset($headers['Authentication-Results']) ? $headers['Authentication-Results'] : ''; if (!empty($authResults)) { - if (stripos($authResults, 'spf=fail') !== false || stripos($authResults, 'spf=softfail') !== false) { + if (mb_stripos($authResults, 'spf=fail') !== false || mb_stripos($authResults, 'spf=softfail') !== false) { $warnings[] = "Kritische Warnung (Header): SPF-Authentifizierung fehlgeschlagen oder Softfail. Die Absenderdomain ist möglicherweise gefälscht."; $phishingScore += 3; } - if (stripos($authResults, 'dkim=fail') !== false) { + if (mb_stripos($authResults, 'dkim=fail') !== false) { $warnings[] = "Kritische Warnung (Header): DKIM-Authentifizierung fehlgeschlagen. Die E-Mail wurde möglicherweise manipuliert oder ist gefälscht."; $phishingScore += 3; } - if (stripos($authResults, 'dmarc=fail') !== false || stripos($authResults, 'dmarc=quarantine') !== false || stripos($authResults, 'dmarc=reject') !== false) { + if (mb_stripos($authResults, 'dmarc=fail') !== false || mb_stripos($authResults, 'dmarc=quarantine') !== false || mb_stripos($authResults, 'dmarc=reject') !== false) { $warnings[] = "Kritische Warnung (Header): DMARC-Authentifizierung fehlgeschlagen oder Aktion ausgelöst (Quarantine/Reject). Dies ist ein starkes Anzeichen für Phishing."; $phishingScore += 4; // Höchster Wert für DMARC Fail } @@ -205,27 +261,43 @@ function checkPhishing($headers, $emailContent) { if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_FILES['email_file']) && $_FILES['email_file']['error'] === UPLOAD_ERR_OK) { $fileTmpPath = $_FILES['email_file']['tmp_name']; - $fileName = $_FILES['email_file']['name']; + $fileName = $_FILES['email_file']['name']; // Original-Dateiname, der Emojis enthalten könnte $fileSize = $_FILES['email_file']['size']; $fileType = $_FILES['email_file']['type']; - $fileNameCmps = explode(".", $fileName); - $fileExtension = strtolower(end($fileNameCmps)); + + // Versuche, die Dateiendung robust zu extrahieren + $fileExtension = ''; + $fileNameParts = explode(".", $fileName); + if (count($fileNameParts) > 1) { + $fileExtension = mb_strtolower(end($fileNameParts)); + } $allowedfileExtensions = array('eml', 'txt'); if (in_array($fileExtension, $allowedfileExtensions)) { - $emailContent = file_get_contents($fileTmpPath); + $emailContent = @file_get_contents($fileTmpPath); // @ zur Unterdrückung von Warnungen if ($emailContent === false) { - header('Location: index.php?error=' . urlencode('Fehler beim Lesen der hochgeladenen Datei.')); + header('Location: index.php?error=' . urlencode('Fehler beim Lesen der hochgeladenen Datei oder Datei ist leer.')); exit(); } + // Sicherstellen, dass $emailContent gültiges UTF-8 ist + if (!mb_check_encoding($emailContent, 'UTF-8')) { + // Versuche, es von ISO-8859-1 oder einer anderen gängigen Kodierung nach UTF-8 zu konvertieren + $emailContent = mb_convert_encoding($emailContent, 'UTF-8', 'auto'); + } + + $headers = parseEmailHeaders($emailContent); $phishingResult = checkPhishing($headers, $emailContent); // Holt jetzt Array mit Warnings und Tendenz // Eine Vorschau des E-Mail-Inhalts für die Debugging-Zwecke - $fullEmailPreview = substr($emailContent, 0, 2000); // Max. 2000 Zeichen + // Sicherstellen, dass der Preview-Inhalt auch UTF-8 ist und gültige Zeichen hat + $fullEmailPreview = mb_substr($emailContent, 0, 2000); // Max. 2000 Zeichen + // Entferne potenziell ungültige UTF-8-Sequenzen, die später Probleme verursachen könnten + $fullEmailPreview = preg_replace('/[^\x{0009}\x{000A}\x{000D}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/u', '', $fullEmailPreview); + $result = [ 'headers' => $headers, @@ -235,7 +307,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ]; // Ergebnisse als JSON enkodieren und base64 enkodieren, um sie in der URL zu übergeben - $encodedResult = base64_encode(json_encode($result)); + $encodedResult = base64_encode(json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)); // JSON_UNESCAPED_UNICODE für Emojis header('Location: index.php?result=' . urlencode($encodedResult)); exit(); @@ -244,7 +316,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { exit(); } } else { - header('Location: index.php?error=' . urlencode('Fehler beim Hochladen der Datei: ' . $_FILES['email_file']['error'])); + $uploadError = 'Unbekannter Fehler beim Hochladen der Datei.'; + if (isset($_FILES['email_file']) && $_FILES['email_file']['error'] !== UPLOAD_ERR_NO_FILE) { + switch ($_FILES['email_file']['error']) { + case UPLOAD_ERR_INI_SIZE: + $uploadError = 'Die hochgeladene Datei überschreitet die in der php.ini festgelegte maximale Dateigröße.'; + break; + case UPLOAD_ERR_FORM_SIZE: + $uploadError = 'Die hochgeladene Datei überschreitet die im HTML-Formular festgelegte maximale Dateigröße.'; + break; + case UPLOAD_ERR_PARTIAL: + $uploadError = 'Die Datei wurde nur teilweise hochgeladen.'; + break; + case UPLOAD_ERR_NO_FILE: + $uploadError = 'Es wurde keine Datei hochgeladen.'; + break; + case UPLOAD_ERR_NO_TMP_DIR: + $uploadError = 'Temporäres Verzeichnis fehlt.'; + break; + case UPLOAD_ERR_CANT_WRITE: + $uploadError = 'Fehler beim Schreiben der Datei auf die Festplatte.'; + break; + case UPLOAD_ERR_EXTENSION: + $uploadError = 'Eine PHP-Erweiterung hat das Hochladen der Datei gestoppt.'; + break; + } + } + header('Location: index.php?error=' . urlencode('Fehler beim Hochladen der Datei: ' . $uploadError)); exit(); } } else {