372 lines
18 KiB
PHP
372 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
|
|
ini_set('memory_limit', '512M');
|
|
set_time_limit(600);
|
|
|
|
// Setze die interne Zeichenkodierung auf UTF-8 für alle Multi-Byte-String-Funktionen
|
|
mb_internal_encoding("UTF-8");
|
|
mb_regex_encoding("UTF-8"); // Auch für Regex-Funktionen
|
|
|
|
// --- Eigene Log-Funktion ---
|
|
function log_message($message) {
|
|
$log_file = __DIR__ . '/email_analysis.log';
|
|
$timestamp = date('Y-m-d H:i:s');
|
|
file_put_contents($log_file, "$timestamp: $message\n", FILE_APPEND | LOCK_EX);
|
|
}
|
|
|
|
function parseEmailHeaders($emailContent) {
|
|
log_message("Starting parseEmailHeaders function.");
|
|
$headers = [];
|
|
if (!is_string($emailContent)) {
|
|
log_message("ERROR: emailContent is not a string in parseEmailHeaders.");
|
|
return $headers;
|
|
}
|
|
|
|
$lines = explode("\n", $emailContent);
|
|
$inHeaders = true;
|
|
$currentHeader = '';
|
|
$headerValueBuffer = '';
|
|
|
|
foreach ($lines as $line) {
|
|
$line = rtrim($line, "\r");
|
|
$line = trim($line);
|
|
|
|
if (empty($line)) {
|
|
if ($currentHeader !== '' && $headerValueBuffer !== '') {
|
|
$headers[$currentHeader] = trim($headerValueBuffer);
|
|
log_message("Parsed header: " . $currentHeader . " = " . mb_substr(trim($headerValueBuffer), 0, 100) . "...");
|
|
}
|
|
$inHeaders = false;
|
|
continue;
|
|
}
|
|
|
|
if ($inHeaders) {
|
|
if (preg_match('/^\s/', $line)) {
|
|
if ($currentHeader !== '') {
|
|
$headerValueBuffer .= ' ' . $line;
|
|
}
|
|
} else {
|
|
if ($currentHeader !== '' && $headerValueBuffer !== '') {
|
|
$headers[$currentHeader] = trim($headerValueBuffer);
|
|
log_message("Parsed header: " . $currentHeader . " = " . mb_substr(trim($headerValueBuffer), 0, 100) . "...");
|
|
}
|
|
|
|
if (preg_match('/^([^:]+):(.*)$/U', $line, $matches)) {
|
|
$headerName = trim($matches[1]);
|
|
$headerValueBuffer = trim($matches[2]);
|
|
$currentHeader = $headerName;
|
|
} else {
|
|
log_message("WARNING: Line in header section did not match header format: " . mb_substr($line, 0, 100) . "...");
|
|
$currentHeader = '';
|
|
$headerValueBuffer = '';
|
|
}
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if ($currentHeader !== '' && $headerValueBuffer !== '' && !isset($headers[$currentHeader])) {
|
|
$headers[$currentHeader] = trim($headerValueBuffer);
|
|
log_message("Parsed final header: " . $currentHeader . " = " . mb_substr(trim($headerValueBuffer), 0, 100) . "...");
|
|
}
|
|
log_message("Finished parseEmailHeaders function. Total headers: " . count($headers));
|
|
return $headers;
|
|
}
|
|
|
|
function checkPhishing($headers, $emailContent) {
|
|
log_message("Starting checkPhishing function.");
|
|
$warnings = [];
|
|
$phishingScore = 0;
|
|
|
|
$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;
|
|
|
|
// --- Prüfung des E-Mail-Inhalts (Body und Betreff) - Priorität 1 ---
|
|
|
|
$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) {
|
|
$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) {
|
|
$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);
|
|
log_message("Phishing check: Found " . $foundContentKeywords . " keywords.");
|
|
}
|
|
|
|
$fromDomain = '';
|
|
if (preg_match('/@([^>]+)/', $from, $fromDomainMatch)) {
|
|
$fromDomain = trim($fromDomainMatch[1]);
|
|
}
|
|
$foundSuspiciousLinks = 0;
|
|
|
|
if (preg_match_all('/https?:\/\/[^\s"\']+/i', $emailContent, $matches)) {
|
|
log_message("Phishing check: Found " . count($matches[0]) . " URLs.");
|
|
foreach (array_unique($matches[0]) as $url) {
|
|
$urlParts = parse_url($url);
|
|
$urlDomain = isset($urlParts['host']) ? mb_strtolower($urlParts['host']) : '';
|
|
|
|
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++;
|
|
}
|
|
|
|
if ($urlDomain && $fromDomain && !empty($fromDomain)) {
|
|
if (mb_strpos($urlDomain, $fromDomain) === false && mb_strpos($fromDomain, $urlDomain) === false) {
|
|
$warnings[] = "Kritische Warnung (Inhalt/Links): Die Domain im Link ('" . htmlspecialchars($urlDomain) . "') weicht von der Absenderdomain ('" . htmlspecialchars($fromDomain) . "') ab. Vorsicht!";
|
|
$phishingScore += 3;
|
|
$foundSuspiciousLinks++;
|
|
}
|
|
}
|
|
$shortenerDomains = ['bit.ly', 'goo.gl', 'tinyurl.com', 'ow.ly', 't.co', 'cutt.ly', 'is.gd', 's.id'];
|
|
foreach ($shortenerDomains as $shortener) {
|
|
if (mb_strpos($urlDomain, $shortener) !== false) {
|
|
$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;
|
|
log_message("Phishing check: Found " . $foundSuspiciousLinks . " suspicious links.");
|
|
}
|
|
|
|
// --- Prüfung der Header-Daten - Priorität 2 ---
|
|
|
|
$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;
|
|
log_message("Phishing check: From vs Return-Path mismatch.");
|
|
}
|
|
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;
|
|
log_message("Phishing check: From vs Reply-To mismatch.");
|
|
}
|
|
|
|
|
|
$importantHeaders = ['From', 'To', 'Subject', 'Date', 'Message-ID'];
|
|
foreach ($importantHeaders as $header) {
|
|
if (!isset($headers[$header]) || empty($headers[$header])) {
|
|
$warnings[] = "Hinweis (Header): Der wichtige Header '" . htmlspecialchars($header) . "' fehlt oder ist leer.";
|
|
$phishingScore += 0.5;
|
|
log_message("Phishing check: Missing/empty important header: " . $header);
|
|
}
|
|
}
|
|
|
|
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;
|
|
log_message("Phishing check: Missing X-Mailer/User-Agent.");
|
|
} else {
|
|
$foundUnusualMailer = false;
|
|
$commonMailers = ['microsoft outlook', 'thunderbird', 'mail.app', 'gmail'];
|
|
|
|
if (!empty($xMailer)) {
|
|
$isCommon = false;
|
|
foreach($commonMailers as $mailer) {
|
|
if (mb_strpos(mb_strtolower($xMailer), $mailer) !== false) {
|
|
$isCommon = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!$isCommon) {
|
|
$foundUnusualMailer = true;
|
|
}
|
|
}
|
|
|
|
if (!$foundUnusualMailer && !empty($userAgent)) {
|
|
$isCommon = false;
|
|
foreach($commonMailers as $mailer) {
|
|
if (mb_strpos(mb_strtolower($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;
|
|
log_message("Phishing check: Unusual X-Mailer/User-Agent.");
|
|
}
|
|
}
|
|
|
|
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;
|
|
log_message("Phishing check: SPF fail/softfail.");
|
|
}
|
|
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;
|
|
log_message("Phishing check: DKIM fail.");
|
|
}
|
|
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;
|
|
log_message("Phishing check: DMARC fail/quarantine/reject.");
|
|
}
|
|
} else {
|
|
$warnings[] = "Hinweis (Header): 'Authentication-Results' Header fehlen. Dies kann auf eine fehlende oder nicht standardmäßige E-Mail-Authentifizierung hindeuten.";
|
|
$phishingScore += 1;
|
|
log_message("Phishing check: Missing Authentication-Results.");
|
|
}
|
|
|
|
$isPhishingTendency = $phishingScore >= 3.0;
|
|
log_message("Finished checkPhishing function. Phishing Score: " . $phishingScore . ", Tendency: " . ($isPhishingTendency ? "HIGH" : "LOW"));
|
|
return ['warnings' => $warnings, 'is_phishing_tendency' => $isPhishingTendency];
|
|
}
|
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
log_message("Script started via POST request.");
|
|
try {
|
|
if (isset($_FILES['email_file']) && $_FILES['email_file']['error'] === UPLOAD_ERR_OK) {
|
|
$fileTmpPath = $_FILES['email_file']['tmp_name'];
|
|
$fileName = $_FILES['email_file']['name'];
|
|
$fileSize = $_FILES['email_file']['size'];
|
|
$fileType = $_FILES['email_file']['type'];
|
|
|
|
log_message("File uploaded: " . $fileName . ", size: " . $fileSize . " bytes.");
|
|
|
|
$fileExtension = '';
|
|
$fileNameParts = explode(".", $fileName);
|
|
if (count($fileNameParts) > 1) {
|
|
$fileExtension = mb_strtolower(end($fileNameParts));
|
|
}
|
|
|
|
$allowedfileExtensions = array('eml', 'txt');
|
|
|
|
if (in_array($fileExtension, $allowedfileExtensions)) {
|
|
log_message("File extension is allowed: " . $fileExtension);
|
|
$emailContent = @file_get_contents($fileTmpPath);
|
|
|
|
if ($emailContent === false) {
|
|
log_message("ERROR: Failed to read file content from " . $fileTmpPath);
|
|
header('Location: index.php?error=' . rawurlencode('Fehler beim Lesen der hochgeladenen Datei oder Datei ist leer.'));
|
|
exit();
|
|
}
|
|
log_message("File content read successfully. Length: " . mb_strlen($emailContent) . " bytes.");
|
|
|
|
|
|
if (!mb_check_encoding($emailContent, 'UTF-8')) {
|
|
log_message("WARNING: Email content not initially UTF-8. Attempting conversion.");
|
|
$emailContent = mb_convert_encoding($emailContent, 'UTF-8', 'auto');
|
|
if (!mb_check_encoding($emailContent, 'UTF-8')) {
|
|
log_message("ERROR: Failed to convert email content to UTF-8 after attempt.");
|
|
} else {
|
|
log_message("Successfully converted email content to UTF-8.");
|
|
}
|
|
}
|
|
|
|
|
|
$headers = parseEmailHeaders($emailContent);
|
|
$phishingResult = checkPhishing($headers, $emailContent);
|
|
|
|
$fullEmailPreview = mb_substr($emailContent, 0, 2000);
|
|
$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
|
|
];
|
|
|
|
// --- NEUE LOGIK: Ergebnis in temporäre Datei schreiben ---
|
|
$tempDir = __DIR__ . '/temp_results/'; // Temporäres Verzeichnis
|
|
if (!is_dir($tempDir)) {
|
|
mkdir($tempDir, 0755, true); // Verzeichnis erstellen, wenn es nicht existiert
|
|
log_message("Created temp_results directory: " . $tempDir);
|
|
}
|
|
|
|
$resultId = uniqid('email_analysis_', true) . '.json'; // Eindeutiger Dateiname
|
|
$tempFilePath = $tempDir . $resultId;
|
|
|
|
// Speichern des JSON-Ergebnisses in der temporären Datei
|
|
$jsonResult = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR);
|
|
if ($jsonResult === false) {
|
|
log_message("ERROR: JSON encoding failed: " . json_last_error_msg());
|
|
throw new Exception("Fehler beim Kodieren der Analyseergebnisse.");
|
|
}
|
|
|
|
if (file_put_contents($tempFilePath, $jsonResult) === false) {
|
|
log_message("ERROR: Failed to write result to temp file: " . $tempFilePath);
|
|
throw new Exception("Fehler beim Speichern der Analyseergebnisse.");
|
|
}
|
|
log_message("Analysis result saved to temp file: " . $tempFilePath);
|
|
|
|
// Weiterleitung mit dem Dateinamen als Parameter
|
|
log_message("Analysis completed. Redirecting to index.php with result ID: " . $resultId);
|
|
header('Location: index.php?result_id=' . rawurlencode($resultId));
|
|
exit();
|
|
|
|
} else {
|
|
log_message("ERROR: Invalid file type uploaded: " . $fileExtension);
|
|
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;
|
|
}
|
|
}
|
|
log_message("ERROR: File upload error: " . $uploadError);
|
|
header('Location: index.php?error=' . rawurlencode('Fehler beim Hochladen der Datei: ' . $uploadError));
|
|
exit();
|
|
}
|
|
} catch (Throwable $e) {
|
|
log_message("FATAL ERROR CAUGHT: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
|
|
header('Location: index.php?error=' . rawurlencode('Ein unerwarteter Fehler ist aufgetreten: ' . $e->getMessage()));
|
|
exit();
|
|
}
|
|
} else {
|
|
log_message("Script accessed directly without POST data.");
|
|
header('Location: index.php');
|
|
exit();
|
|
} |