730 lines
22 KiB
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;
|
|
}
|
|
}
|