Files
Mailanalyse/process_email.php
2025-06-26 19:21:29 +02:00

353 lines
18 KiB
PHP

<?php
// PHP-Fehler anzeigen (nur für Entwicklung, im Produktivsystem deaktivieren)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Erhöhen Sie das Speicherlimit und die Ausführungszeit für potenziell große E-Mails
// NEUE WERTE: 512MB Speicher, 300 Sekunden (5 Minuten) Ausführungszeit
ini_set('memory_limit', '512M');
set_time_limit(300);
// 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)) {
// Leere Zeile bedeutet Ende der Header. Verarbeite den letzten Header im Puffer.
if ($currentHeader !== '' && $headerValueBuffer !== '') {
$headers[$currentHeader] = trim($headerValueBuffer);
}
$inHeaders = false;
continue;
}
if ($inHeaders) {
// Wenn die Zeile mit Leerzeichen oder Tab beginnt, ist es eine Fortsetzung des vorherigen Headers
if (preg_match('/^\s/', $line)) {
if ($currentHeader !== '') {
$headerValueBuffer .= ' ' . $line; // Füge zur Puffer hinzu
}
} else {
// 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]);
$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, 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 {
// Nach den Headern sind wir im Body, wir brauchen nur die Header
break;
}
}
// Nach der Schleife den letzten gesammelten Header hinzufügen, falls vorhanden
if ($currentHeader !== '' && $headerValueBuffer !== '' && !isset($headers[$currentHeader])) {
$headers[$currentHeader] = trim($headerValueBuffer);
}
return $headers;
}
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 (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 (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++;
}
}
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)
// 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)
$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']) ? 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)) {
$warnings[] = "Kritische Warnung (Inhalt/Links): Ein Link enthält eine direkte IP-Adresse ('" . htmlspecialchars($url) . "'). Dies ist oft verdächtig.";
$phishingScore += 2;
$foundSuspiciousLinks++;
}
// Check if the URL domain is significantly different from the From domain
if ($urlDomain && $fromDomain && !empty($fromDomain)) {
// Eine robustere Prüfung könnte hier auch Subdomain-Tricks erkennen
// z.B. bank.com.malicious.example.com
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++;
}
}
// 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 (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++;
break;
}
}
}
}
if ($foundSuspiciousLinks > 0) $phishingScore += 2; // Zusätzlicher Punkt für Links
// --- Prüfung der Header-Daten - Priorität 2 ---
// 4. Absenderprüfung (From vs. Return-Path vs. Reply-To)
$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.";
$phishingScore += 2;
}
if ($fromDomain && $replyToDomain && $fromDomain !== $replyToDomain) {
$warnings[] = "Warnung (Header): Die Absenderdomain ('" . htmlspecialchars($fromDomain) . "') stimmt nicht mit der 'Reply-To'-Domain ('" . htmlspecialchars($replyToDomain) . "') überein. Eine Diskrepanz kann auf Betrug hindeuten.";
$phishingScore += 2;
}
// 5. Fehlende oder seltsame Header
$importantHeaders = ['From', 'To', 'Subject', 'Date', 'Message-ID'];
foreach ($importantHeaders as $header) {
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
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;
} 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)
if (!empty($authResults)) {
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 (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 (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
}
} else {
$warnings[] = "Hinweis (Header): 'Authentication-Results' Header fehlen. Dies kann auf eine fehlende oder nicht standardmäßige E-Mail-Authentifizierung hindeuten.";
$phishingScore += 1;
}
// --- Bestimmung der Phishing-Tendenz ---
// Dies ist ein einfacher Schwellenwert. Sie können die Werte und Schwellenwerte anpassen.
// Ein Wert > 3 deutet auf eine hohe Phishing-Wahrscheinlichkeit hin.
$isPhishingTendency = $phishingScore >= 3.0; // Schwellenwert für "hoch"
return ['warnings' => $warnings, 'is_phishing_tendency' => $isPhishingTendency];
}
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']; // Original-Dateiname, der Emojis enthalten könnte
$fileSize = $_FILES['email_file']['size'];
$fileType = $_FILES['email_file']['type'];
// 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); // @ zur Unterdrückung von Warnungen
if ($emailContent === false) {
header('Location: index.php?error=' . rawurlencode('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
// 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,
'phishing_check' => $phishingResult['warnings'],
'is_phishing_tendency' => $phishingResult['is_phishing_tendency'],
'full_email_preview' => $fullEmailPreview
];
// Ergebnisse als JSON enkodieren und base64 enkodieren, um sie in der URL zu übergeben
// NEU: rawurlencode() statt urlencode() für robustere URL-Parameter
$encodedResult = rawurlencode(base64_encode(json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)));
header('Location: index.php?result=' . $encodedResult);
exit();
} else {
header('Location: index.php?error=' . rawurlencode('Ungültiger Dateityp. Bitte laden Sie eine .eml- oder .txt-Datei hoch.'));
exit();
}
} else {
$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=' . rawurlencode('Fehler beim Hochladen der Datei: ' . $uploadError));
exit();
}
} else {
header('Location: index.php');
exit();
}