Newwebshop/app/Core/SecuritySystem.php

730 lines
22 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Security-System für PrestaShop-Modul-Kompatibilität
*
* @author Webshop System
* @license GPL v3
*/
namespace App\Core;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
class SecuritySystem
{
private static $instance = null;
private $eventDispatcher;
private $cache;
private $logger;
private $enabled = true;
private $codeSigningEnabled = true;
private $malwareScanningEnabled = true;
private $sandboxEnabled = true;
private $publicKeyPath;
private $privateKeyPath;
private function __construct()
{
$this->eventDispatcher = EventDispatcher::getInstance();
$this->cache = Cache::getInstance();
$this->logger = Logger::getInstance();
$this->publicKeyPath = __DIR__ . '/../../../security/keys/public.pem';
$this->privateKeyPath = __DIR__ . '/../../../security/keys/private.pem';
$this->loadSettings();
}
/**
* Singleton-Instanz abrufen
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Einstellungen laden
*/
private function loadSettings()
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
SELECT setting_key, setting_value
FROM ws_security_settings
WHERE active = 1
');
$stmt->execute();
$settings = $stmt->fetchAllAssociative();
foreach ($settings as $setting) {
switch ($setting['setting_key']) {
case 'enabled':
$this->enabled = (bool)$setting['setting_value'];
break;
case 'code_signing_enabled':
$this->codeSigningEnabled = (bool)$setting['setting_value'];
break;
case 'malware_scanning_enabled':
$this->malwareScanningEnabled = (bool)$setting['setting_value'];
break;
case 'sandbox_enabled':
$this->sandboxEnabled = (bool)$setting['setting_value'];
break;
}
}
} catch (Exception $e) {
$this->logger->error('Security-Einstellungen laden Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Code signieren
*/
public function signCode($filePath, $moduleName)
{
if (!$this->codeSigningEnabled) {
return ['success' => true, 'signed' => false];
}
try {
// Datei-Hash erstellen
$fileHash = hash_file('sha256', $filePath);
// Signatur erstellen
$signature = $this->createSignature($fileHash, $moduleName);
// Signatur in Datei einbetten
$this->embedSignature($filePath, $signature);
// Signatur in Datenbank speichern
$this->saveSignature($moduleName, $filePath, $signature, $fileHash);
$this->logger->info('Code signiert', [
'module_name' => $moduleName,
'file_path' => $filePath,
'signature' => substr($signature, 0, 32) . '...'
]);
return ['success' => true, 'signed' => true, 'signature' => $signature];
} catch (\Exception $e) {
$this->logger->error('Code-Signierung Fehler', [
'module_name' => $moduleName,
'file_path' => $filePath,
'error' => $e->getMessage()
]);
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* Code-Signatur erstellen
*/
private function createSignature($fileHash, $moduleName)
{
if (!file_exists($this->privateKeyPath)) {
throw new \Exception('Private Key nicht gefunden');
}
$privateKey = openssl_pkey_get_private(file_get_contents($this->privateKeyPath));
if (!$privateKey) {
throw new \Exception('Private Key ungültig');
}
$data = $fileHash . '|' . $moduleName . '|' . time();
$signature = '';
$result = openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);
openssl_free_key($privateKey);
if (!$result) {
throw new \Exception('Signatur-Erstellung fehlgeschlagen');
}
return base64_encode($signature);
}
/**
* Signatur in Datei einbetten
*/
private function embedSignature($filePath, $signature)
{
$content = file_get_contents($filePath);
// Signatur-Kommentar hinzufügen
$signatureComment = "\n// SIGNATURE: " . $signature . "\n";
// Am Ende der Datei hinzufügen
$content .= $signatureComment;
file_put_contents($filePath, $content);
}
/**
* Signatur in Datenbank speichern
*/
private function saveSignature($moduleName, $filePath, $signature, $fileHash)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
INSERT INTO ws_code_signatures (
module_name, file_path, signature, file_hash, created_at
) VALUES (?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
signature = ?, file_hash = ?, updated_at = NOW()
');
$stmt->execute([
$moduleName,
$filePath,
$signature,
$fileHash,
$signature,
$fileHash
]);
} catch (Exception $e) {
$this->logger->error('Signatur speichern Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Code-Signatur verifizieren
*/
public function verifySignature($filePath, $moduleName)
{
if (!$this->codeSigningEnabled) {
return ['success' => true, 'verified' => true];
}
try {
// Signatur aus Datei extrahieren
$signature = $this->extractSignature($filePath);
if (!$signature) {
return ['success' => false, 'error' => 'Keine Signatur gefunden'];
}
// Datei-Hash erstellen (ohne Signatur)
$content = file_get_contents($filePath);
$content = preg_replace('/\/\/ SIGNATURE: .*$/m', '', $content);
$fileHash = hash('sha256', $content);
// Signatur verifizieren
$verified = $this->verifySignatureData($signature, $fileHash, $moduleName);
if ($verified) {
$this->logger->info('Code-Signatur verifiziert', [
'module_name' => $moduleName,
'file_path' => $filePath
]);
return ['success' => true, 'verified' => true];
} else {
return ['success' => false, 'error' => 'Signatur ungültig'];
}
} catch (\Exception $e) {
$this->logger->error('Code-Signatur-Verifikation Fehler', [
'module_name' => $moduleName,
'file_path' => $filePath,
'error' => $e->getMessage()
]);
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* Signatur aus Datei extrahieren
*/
private function extractSignature($filePath)
{
$content = file_get_contents($filePath);
if (preg_match('/\/\/ SIGNATURE: (.+)$/m', $content, $matches)) {
return $matches[1];
}
return null;
}
/**
* Signatur-Daten verifizieren
*/
private function verifySignatureData($signature, $fileHash, $moduleName)
{
if (!file_exists($this->publicKeyPath)) {
throw new \Exception('Public Key nicht gefunden');
}
$publicKey = openssl_pkey_get_public(file_get_contents($this->publicKeyPath));
if (!$publicKey) {
throw new \Exception('Public Key ungültig');
}
$signatureData = base64_decode($signature);
$data = $fileHash . '|' . $moduleName . '|' . time();
$result = openssl_verify($data, $signatureData, $publicKey, OPENSSL_ALGO_SHA256);
openssl_free_key($publicKey);
return $result === 1;
}
/**
* Malware-Scan durchführen
*/
public function scanForMalware($filePath, $moduleName)
{
if (!$this->malwareScanningEnabled) {
return ['success' => true, 'clean' => true];
}
try {
// Event auslösen
$this->eventDispatcher->dispatch('security.malware.scan.before', [
'file_path' => $filePath,
'module_name' => $moduleName
]);
$threats = [];
// Datei-Inhalt scannen
$content = file_get_contents($filePath);
// Bekannte Malware-Patterns prüfen
$threats = array_merge($threats, $this->scanForPatterns($content));
// PHP-Code-Analyse
$threats = array_merge($threats, $this->analyzePhpCode($content));
// Datei-Hash prüfen
$threats = array_merge($threats, $this->checkFileHash($filePath));
// Sandbox-Test
if ($this->sandboxEnabled) {
$threats = array_merge($threats, $this->sandboxTest($filePath));
}
$isClean = empty($threats);
// Scan-Ergebnis speichern
$this->saveScanResult($moduleName, $filePath, $threats, $isClean);
// Event auslösen
$this->eventDispatcher->dispatch('security.malware.scan.after', [
'file_path' => $filePath,
'module_name' => $moduleName,
'threats' => $threats,
'is_clean' => $isClean
]);
if ($isClean) {
$this->logger->info('Malware-Scan abgeschlossen - Datei ist sauber', [
'module_name' => $moduleName,
'file_path' => $filePath
]);
} else {
$this->logger->warning('Malware-Scan abgeschlossen - Bedrohungen gefunden', [
'module_name' => $moduleName,
'file_path' => $filePath,
'threats' => $threats
]);
}
return [
'success' => true,
'clean' => $isClean,
'threats' => $threats
];
} catch (\Exception $e) {
$this->logger->error('Malware-Scan Fehler', [
'module_name' => $moduleName,
'file_path' => $filePath,
'error' => $e->getMessage()
]);
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* Pattern-basierte Malware-Erkennung
*/
private function scanForPatterns($content)
{
$threats = [];
$malwarePatterns = [
'eval\s*\(' => 'Eval-Funktion gefunden',
'exec\s*\(' => 'Exec-Funktion gefunden',
'system\s*\(' => 'System-Funktion gefunden',
'shell_exec\s*\(' => 'Shell-Exec-Funktion gefunden',
'passthru\s*\(' => 'Passthru-Funktion gefunden',
'base64_decode\s*\(' => 'Base64-Decode gefunden',
'gzinflate\s*\(' => 'Gzinflate-Funktion gefunden',
'str_rot13\s*\(' => 'ROT13-Verschlüsselung gefunden',
'file_get_contents\s*\(\s*[\'"]https?://' => 'Externe URL-Zugriffe gefunden',
'curl_exec\s*\(' => 'CURL-Exec gefunden',
'fopen\s*\(\s*[\'"]https?://' => 'Externe Datei-Öffnung gefunden'
];
foreach ($malwarePatterns as $pattern => $description) {
if (preg_match('/' . $pattern . '/i', $content)) {
$threats[] = [
'type' => 'pattern',
'description' => $description,
'severity' => 'medium'
];
}
}
return $threats;
}
/**
* PHP-Code-Analyse
*/
private function analyzePhpCode($content)
{
$threats = [];
// AST-Analyse (vereinfacht)
$tokens = token_get_all($content);
foreach ($tokens as $token) {
if (is_array($token)) {
$tokenType = $token[0];
$tokenValue = $token[1];
// Gefährliche Funktionen prüfen
$dangerousFunctions = ['eval', 'exec', 'system', 'shell_exec', 'passthru'];
if ($tokenType === T_STRING && in_array(strtolower($tokenValue), $dangerousFunctions)) {
$threats[] = [
'type' => 'dangerous_function',
'description' => 'Gefährliche Funktion gefunden: ' . $tokenValue,
'severity' => 'high'
];
}
}
}
return $threats;
}
/**
* Datei-Hash prüfen
*/
private function checkFileHash($filePath)
{
$threats = [];
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$fileHash = hash_file('sha256', $filePath);
// Bekannte Malware-Hashes prüfen
$stmt = $conn->prepare('
SELECT description FROM ws_malware_hashes
WHERE hash = ? AND active = 1
');
$stmt->execute([$fileHash]);
$malwareHashes = $stmt->fetchAllAssociative();
foreach ($malwareHashes as $malware) {
$threats[] = [
'type' => 'known_malware',
'description' => 'Bekannte Malware: ' . $malware['description'],
'severity' => 'critical'
];
}
} catch (Exception $e) {
$this->logger->error('Datei-Hash-Prüfung Fehler', [
'error' => $e->getMessage()
]);
}
return $threats;
}
/**
* Sandbox-Test
*/
private function sandboxTest($filePath)
{
$threats = [];
try {
// Isolierte Umgebung erstellen
$sandboxDir = __DIR__ . '/../../../security/sandbox/';
if (!is_dir($sandboxDir)) {
mkdir($sandboxDir, 0755, true);
}
$sandboxFile = $sandboxDir . 'test_' . uniqid() . '.php';
copy($filePath, $sandboxFile);
// Sandbox-Ausführung (sehr eingeschränkt)
$output = [];
$returnCode = 0;
// Nur Syntax-Check
exec('php -l ' . escapeshellarg($sandboxFile) . ' 2>&1', $output, $returnCode);
if ($returnCode !== 0) {
$threats[] = [
'type' => 'syntax_error',
'description' => 'PHP-Syntax-Fehler: ' . implode(' ', $output),
'severity' => 'medium'
];
}
// Sandbox-Datei löschen
unlink($sandboxFile);
} catch (\Exception $e) {
$threats[] = [
'type' => 'sandbox_error',
'description' => 'Sandbox-Test fehlgeschlagen: ' . $e->getMessage(),
'severity' => 'low'
];
}
return $threats;
}
/**
* Scan-Ergebnis speichern
*/
private function saveScanResult($moduleName, $filePath, $threats, $isClean)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
INSERT INTO ws_security_scans (
module_name, file_path, threats, is_clean, scan_date
) VALUES (?, ?, ?, ?, NOW())
');
$stmt->execute([
$moduleName,
$filePath,
json_encode($threats),
$isClean ? 1 : 0
]);
} catch (Exception $e) {
$this->logger->error('Scan-Ergebnis speichern Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Security-Scan für Modul durchführen
*/
public function scanModule($moduleName, $modulePath)
{
$results = [
'signature' => null,
'malware' => null,
'overall_clean' => true
];
// Hauptdatei scannen
$mainFile = $modulePath . '/Module.php';
if (file_exists($mainFile)) {
// Code signieren
$signatureResult = $this->signCode($mainFile, $moduleName);
$results['signature'] = $signatureResult;
// Malware-Scan
$malwareResult = $this->scanForMalware($mainFile, $moduleName);
$results['malware'] = $malwareResult;
if (!$malwareResult['clean']) {
$results['overall_clean'] = false;
}
}
// Alle PHP-Dateien scannen
$phpFiles = $this->findPhpFiles($modulePath);
foreach ($phpFiles as $file) {
$malwareResult = $this->scanForMalware($file, $moduleName);
if (!$malwareResult['clean']) {
$results['overall_clean'] = false;
}
}
return $results;
}
/**
* PHP-Dateien finden
*/
private function findPhpFiles($directory)
{
$files = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory)
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$files[] = $file->getPathname();
}
}
return $files;
}
/**
* Security-Einstellungen speichern
*/
public function saveSettings($settings)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
foreach ($settings as $key => $value) {
$stmt = $conn->prepare('
INSERT INTO ws_security_settings (
setting_key, setting_value, active, updated_at
) VALUES (?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE
setting_value = ?, updated_at = NOW()
');
$stmt->execute([$key, $value, $value]);
}
// Einstellungen neu laden
$this->loadSettings();
$this->logger->info('Security-Einstellungen gespeichert', [
'settings' => $settings
]);
return true;
} catch (Exception $e) {
$this->logger->error('Security-Einstellungen speichern Fehler', [
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Security-System aktivieren/deaktivieren
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* Security-System Status prüfen
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* Code-Signierung aktivieren/deaktivieren
*/
public function setCodeSigningEnabled($enabled)
{
$this->codeSigningEnabled = $enabled;
return $this;
}
/**
* Code-Signierung Status prüfen
*/
public function isCodeSigningEnabled()
{
return $this->codeSigningEnabled;
}
/**
* Malware-Scanning aktivieren/deaktivieren
*/
public function setMalwareScanningEnabled($enabled)
{
$this->malwareScanningEnabled = $enabled;
return $this;
}
/**
* Malware-Scanning Status prüfen
*/
public function isMalwareScanningEnabled()
{
return $this->malwareScanningEnabled;
}
/**
* Sandbox aktivieren/deaktivieren
*/
public function setSandboxEnabled($enabled)
{
$this->sandboxEnabled = $enabled;
return $this;
}
/**
* Sandbox Status prüfen
*/
public function isSandboxEnabled()
{
return $this->sandboxEnabled;
}
}