Newwebshop/app/controllers/admin/SecurityController.php

760 lines
23 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Security Controller für das Admin-Bereich
*
* @author Webshop System
* @license GPL v3
*/
namespace App\Controllers\Admin;
use App\Core\Security;
use App\Core\Backup;
use App\Core\SessionHandler;
use App\Core\Configuration;
class SecurityController extends AdminController
{
private $security;
private $backup;
public function __construct()
{
parent::__construct();
$this->security = new Security();
$this->backup = new Backup();
}
/**
* Security Dashboard
*/
public function dashboard()
{
if (!$this->checkAdminSession()) {
$this->redirect('/admin/login');
}
$securityStatus = $this->getSecurityStatus();
$backupStatus = $this->backup->checkBackupStatus();
$recentEvents = $this->getRecentSecurityEvents();
$rateLimitStats = $this->getRateLimitStatistics();
$this->render('admin/security/dashboard.html.twig', [
'title' => 'Sicherheits-Dashboard',
'security_status' => $securityStatus,
'backup_status' => $backupStatus,
'recent_events' => $recentEvents,
'rate_limit_stats' => $rateLimitStats
]);
}
/**
* Security Settings
*/
public function settings()
{
if (!$this->checkAdminSession()) {
$this->redirect('/admin/login');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$this->updateSecuritySettings();
}
$settings = $this->getSecuritySettings();
$this->render('admin/security/settings.html.twig', [
'title' => 'Sicherheitseinstellungen',
'settings' => $settings
]);
}
/**
* Backup Management
*/
public function backup()
{
if (!$this->checkAdminSession()) {
$this->redirect('/admin/login');
}
$action = $_GET['action'] ?? '';
switch ($action) {
case 'create':
$this->createBackup();
break;
case 'restore':
$this->restoreBackup();
break;
case 'download':
$this->downloadBackup();
break;
case 'delete':
$this->deleteBackup();
break;
default:
$this->showBackupList();
}
}
/**
* Security Logs
*/
public function logs()
{
if (!$this->checkAdminSession()) {
$this->redirect('/admin/login');
}
$page = max(1, intval($_GET['page'] ?? 1));
$limit = 50;
$offset = ($page - 1) * $limit;
$filters = [
'level' => $_GET['level'] ?? '',
'event' => $_GET['event'] ?? '',
'date_from' => $_GET['date_from'] ?? '',
'date_to' => $_GET['date_to'] ?? ''
];
$logs = $this->getSecurityLogs($filters, $limit, $offset);
$totalLogs = $this->getSecurityLogsCount($filters);
$this->render('admin/security/logs.html.twig', [
'title' => 'Sicherheits-Logs',
'logs' => $logs,
'filters' => $filters,
'pagination' => [
'page' => $page,
'limit' => $limit,
'total' => $totalLogs,
'pages' => ceil($totalLogs / $limit)
]
]);
}
/**
* Rate Limiting Management
*/
public function rateLimiting()
{
if (!$this->checkAdminSession()) {
$this->redirect('/admin/login');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$this->updateRateLimiting();
}
$rateLimits = $this->getRateLimitingConfig();
$statistics = $this->getRateLimitStatistics();
$this->render('admin/security/rate_limiting.html.twig', [
'title' => 'Rate Limiting',
'rate_limits' => $rateLimits,
'statistics' => $statistics
]);
}
/**
* SSL/TLS Configuration
*/
public function ssl()
{
if (!$this->checkAdminSession()) {
$this->redirect('/admin/login');
}
$sslStatus = $this->getSSLStatus();
$sslConfig = $this->getSSLConfiguration();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$this->updateSSLConfiguration();
}
$this->render('admin/security/ssl.html.twig', [
'title' => 'SSL/TLS Konfiguration',
'ssl_status' => $sslStatus,
'ssl_config' => $sslConfig
]);
}
/**
* Security Status abrufen
*/
private function getSecurityStatus()
{
$status = [
'ssl_enabled' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
'security_headers' => $this->checkSecurityHeaders(),
'session_security' => $this->checkSessionSecurity(),
'file_permissions' => $this->checkFilePermissions(),
'database_security' => $this->checkDatabaseSecurity(),
'backup_status' => $this->backup->checkBackupStatus()
];
$status['overall_score'] = $this->calculateSecurityScore($status);
return $status;
}
/**
* Security Headers prüfen
*/
private function checkSecurityHeaders()
{
$headers = [
'Strict-Transport-Security' => false,
'X-Content-Type-Options' => false,
'X-Frame-Options' => false,
'X-XSS-Protection' => false,
'Referrer-Policy' => false,
'Content-Security-Policy' => false
];
$responseHeaders = headers_list();
foreach ($responseHeaders as $header) {
$parts = explode(':', $header, 2);
if (count($parts) === 2) {
$name = trim($parts[0]);
if (isset($headers[$name])) {
$headers[$name] = true;
}
}
}
return $headers;
}
/**
* Session Security prüfen
*/
private function checkSessionSecurity()
{
return [
'httponly' => ini_get('session.cookie_httponly') == '1',
'secure' => ini_get('session.cookie_secure') == '1',
'samesite' => ini_get('session.cookie_samesite') === 'Strict',
'strict_mode' => ini_get('session.use_strict_mode') == '1'
];
}
/**
* File Permissions prüfen
*/
private function checkFilePermissions()
{
$criticalFiles = [
__DIR__ . '/../../../config/database.php' => '0400',
__DIR__ . '/../../../.env' => '0400',
__DIR__ . '/../../../backups' => '0755'
];
$permissions = [];
foreach ($criticalFiles as $file => $expected) {
if (file_exists($file)) {
$actual = substr(sprintf('%o', fileperms($file)), -4);
$permissions[$file] = [
'expected' => $expected,
'actual' => $actual,
'secure' => $actual <= $expected
];
}
}
return $permissions;
}
/**
* Database Security prüfen
*/
private function checkDatabaseSecurity()
{
try {
$stmt = $this->conn->prepare('
SELECT COUNT(*) as user_count
FROM ws_user
WHERE role = "admin"
');
$stmt->execute();
$adminCount = $stmt->fetchAssociative()['user_count'];
$stmt = $this->conn->prepare('
SELECT COUNT(*) as failed_logins
FROM ws_security_log
WHERE event = "login_failed"
AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)
');
$stmt->execute();
$failedLogins = $stmt->fetchAssociative()['failed_logins'];
return [
'admin_users' => $adminCount,
'recent_failed_logins' => $failedLogins,
'secure' => $adminCount > 0 && $failedLogins < 100
];
} catch (Exception $e) {
return [
'admin_users' => 0,
'recent_failed_logins' => 0,
'secure' => false,
'error' => $e->getMessage()
];
}
}
/**
* Security Score berechnen
*/
private function calculateSecurityScore($status)
{
$score = 0;
$total = 0;
// SSL Score
$total++;
if ($status['ssl_enabled']) {
$score++;
}
// Security Headers Score
$headerScore = 0;
$headerTotal = count($status['security_headers']);
foreach ($status['security_headers'] as $enabled) {
if ($enabled) $headerScore++;
}
$score += ($headerScore / $headerTotal);
$total++;
// Session Security Score
$sessionScore = 0;
$sessionTotal = count($status['session_security']);
foreach ($status['session_security'] as $enabled) {
if ($enabled) $sessionScore++;
}
$score += ($sessionScore / $sessionTotal);
$total++;
// File Permissions Score
$fileScore = 0;
$fileTotal = count($status['file_permissions']);
foreach ($status['file_permissions'] as $file) {
if ($file['secure']) $fileScore++;
}
$score += ($fileScore / $fileTotal);
$total++;
// Database Security Score
$total++;
if ($status['database_security']['secure']) {
$score++;
}
return round(($score / $total) * 100, 1);
}
/**
* Recent Security Events
*/
private function getRecentSecurityEvents()
{
try {
$stmt = $this->conn->prepare('
SELECT event, level, ip_address, user_agent, created_at
FROM ws_security_log
ORDER BY created_at DESC
LIMIT 10
');
$stmt->execute();
return $stmt->fetchAllAssociative();
} catch (Exception $e) {
return [];
}
}
/**
* Rate Limit Statistics
*/
private function getRateLimitStatistics()
{
try {
$stmt = $this->conn->prepare('
SELECT action, COUNT(*) as attempts,
COUNT(CASE WHEN created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR) THEN 1 END) as recent_attempts
FROM ws_rate_limit
WHERE created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
GROUP BY action
');
$stmt->execute();
return $stmt->fetchAllAssociative();
} catch (Exception $e) {
return [];
}
}
/**
* Security Settings abrufen
*/
private function getSecuritySettings()
{
return [
'csrf_protection' => $this->config->get('CSRF_PROTECTION', true),
'rate_limiting' => $this->config->get('RATE_LIMITING', true),
'session_timeout' => $this->config->get('SESSION_TIMEOUT', 3600),
'max_login_attempts' => $this->config->get('MAX_LOGIN_ATTEMPTS', 5),
'password_min_length' => $this->config->get('PASSWORD_MIN_LENGTH', 8),
'require_strong_password' => $this->config->get('REQUIRE_STRONG_PASSWORD', true),
'two_factor_auth' => $this->config->get('TWO_FACTOR_AUTH', false),
'ssl_required' => $this->config->get('SSL_REQUIRED', true),
'security_headers' => $this->config->get('SECURITY_HEADERS', true),
'file_upload_security' => $this->config->get('FILE_UPLOAD_SECURITY', true)
];
}
/**
* Security Settings aktualisieren
*/
private function updateSecuritySettings()
{
$settings = [
'CSRF_PROTECTION' => isset($_POST['csrf_protection']),
'RATE_LIMITING' => isset($_POST['rate_limiting']),
'SESSION_TIMEOUT' => intval($_POST['session_timeout'] ?? 3600),
'MAX_LOGIN_ATTEMPTS' => intval($_POST['max_login_attempts'] ?? 5),
'PASSWORD_MIN_LENGTH' => intval($_POST['password_min_length'] ?? 8),
'REQUIRE_STRONG_PASSWORD' => isset($_POST['require_strong_password']),
'TWO_FACTOR_AUTH' => isset($_POST['two_factor_auth']),
'SSL_REQUIRED' => isset($_POST['ssl_required']),
'SECURITY_HEADERS' => isset($_POST['security_headers']),
'FILE_UPLOAD_SECURITY' => isset($_POST['file_upload_security'])
];
foreach ($settings as $key => $value) {
$this->config->set($key, $value);
}
$this->addFlashMessage('Sicherheitseinstellungen wurden aktualisiert', 'success');
$this->redirect('/admin/security/settings');
}
/**
* Backup erstellen
*/
private function createBackup()
{
$description = $_POST['description'] ?? 'Manuelles Backup';
$result = $this->backup->createFullBackup($description);
if ($result['success']) {
$this->addFlashMessage('Backup erfolgreich erstellt: ' . $result['backup_name'], 'success');
} else {
$this->addFlashMessage('Backup fehlgeschlagen: ' . $result['error'], 'error');
}
$this->redirect('/admin/security/backup');
}
/**
* Backup wiederherstellen
*/
private function restoreBackup()
{
$backupFile = $_POST['backup_file'] ?? '';
if (!$backupFile || !file_exists($backupFile)) {
$this->addFlashMessage('Backup-Datei nicht gefunden', 'error');
$this->redirect('/admin/security/backup');
}
$options = [
'restore_database' => isset($_POST['restore_database']),
'restore_files' => isset($_POST['restore_files']),
'restore_config' => isset($_POST['restore_config'])
];
$result = $this->backup->restoreBackup($backupFile, $options);
if ($result['success']) {
$this->addFlashMessage('Backup erfolgreich wiederhergestellt', 'success');
} else {
$this->addFlashMessage('Backup-Wiederherstellung fehlgeschlagen: ' . $result['error'], 'error');
}
$this->redirect('/admin/security/backup');
}
/**
* Backup herunterladen
*/
private function downloadBackup()
{
$backupFile = $_GET['file'] ?? '';
if (!$backupFile || !file_exists($backupFile)) {
$this->addFlashMessage('Backup-Datei nicht gefunden', 'error');
$this->redirect('/admin/security/backup');
}
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($backupFile) . '"');
header('Content-Length: ' . filesize($backupFile));
readfile($backupFile);
exit;
}
/**
* Backup löschen
*/
private function deleteBackup()
{
$backupFile = $_POST['backup_file'] ?? '';
if (!$backupFile || !file_exists($backupFile)) {
$this->addFlashMessage('Backup-Datei nicht gefunden', 'error');
$this->redirect('/admin/security/backup');
}
if (unlink($backupFile)) {
$this->addFlashMessage('Backup erfolgreich gelöscht', 'success');
} else {
$this->addFlashMessage('Fehler beim Löschen des Backups', 'error');
}
$this->redirect('/admin/security/backup');
}
/**
* Backup-Liste anzeigen
*/
private function showBackupList()
{
$backups = $this->backup->getBackupList();
$backupStatus = $this->backup->checkBackupStatus();
$this->render('admin/security/backup.html.twig', [
'title' => 'Backup-Verwaltung',
'backups' => $backups,
'backup_status' => $backupStatus
]);
}
/**
* Security Logs abrufen
*/
private function getSecurityLogs($filters, $limit, $offset)
{
try {
$whereConditions = [];
$params = [];
if (!empty($filters['level'])) {
$whereConditions[] = 'level = ?';
$params[] = $filters['level'];
}
if (!empty($filters['event'])) {
$whereConditions[] = 'event LIKE ?';
$params[] = '%' . $filters['event'] . '%';
}
if (!empty($filters['date_from'])) {
$whereConditions[] = 'created_at >= ?';
$params[] = $filters['date_from'] . ' 00:00:00';
}
if (!empty($filters['date_to'])) {
$whereConditions[] = 'created_at <= ?';
$params[] = $filters['date_to'] . ' 23:59:59';
}
$whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : '';
$sql = "
SELECT id, event, level, ip_address, user_agent, created_at
FROM ws_security_log
$whereClause
ORDER BY created_at DESC
LIMIT $limit OFFSET $offset
";
$stmt = $this->conn->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAllAssociative();
} catch (Exception $e) {
return [];
}
}
/**
* Security Logs Count
*/
private function getSecurityLogsCount($filters)
{
try {
$whereConditions = [];
$params = [];
if (!empty($filters['level'])) {
$whereConditions[] = 'level = ?';
$params[] = $filters['level'];
}
if (!empty($filters['event'])) {
$whereConditions[] = 'event LIKE ?';
$params[] = '%' . $filters['event'] . '%';
}
if (!empty($filters['date_from'])) {
$whereConditions[] = 'created_at >= ?';
$params[] = $filters['date_from'] . ' 00:00:00';
}
if (!empty($filters['date_to'])) {
$whereConditions[] = 'created_at <= ?';
$params[] = $filters['date_to'] . ' 23:59:59';
}
$whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : '';
$sql = "SELECT COUNT(*) as total FROM ws_security_log $whereClause";
$stmt = $this->conn->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAssociative()['total'];
} catch (Exception $e) {
return 0;
}
}
/**
* Rate Limiting Config abrufen
*/
private function getRateLimitingConfig()
{
return [
'login' => ['max_attempts' => 5, 'window' => 300],
'api' => ['max_attempts' => 100, 'window' => 3600],
'register' => ['max_attempts' => 3, 'window' => 1800],
'password_reset' => ['max_attempts' => 3, 'window' => 3600],
'review' => ['max_attempts' => 10, 'window' => 3600],
'contact' => ['max_attempts' => 5, 'window' => 1800]
];
}
/**
* Rate Limiting aktualisieren
*/
private function updateRateLimiting()
{
$actions = ['login', 'api', 'register', 'password_reset', 'review', 'contact'];
foreach ($actions as $action) {
$maxAttempts = intval($_POST[$action . '_max_attempts'] ?? 5);
$window = intval($_POST[$action . '_window'] ?? 300);
// In Konfiguration speichern
$this->config->set('RATE_LIMIT_' . strtoupper($action) . '_MAX', $maxAttempts);
$this->config->set('RATE_LIMIT_' . strtoupper($action) . '_WINDOW', $window);
}
$this->addFlashMessage('Rate Limiting-Einstellungen aktualisiert', 'success');
$this->redirect('/admin/security/rate_limiting');
}
/**
* SSL Status abrufen
*/
private function getSSLStatus()
{
return [
'enabled' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
'certificate' => $this->getSSLCertificateInfo(),
'headers' => $this->checkSecurityHeaders()
];
}
/**
* SSL Certificate Info
*/
private function getSSLCertificateInfo()
{
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
return null;
}
$host = $_SERVER['HTTP_HOST'];
$context = stream_context_create(['ssl' => ['capture_peer_cert' => true]]);
$socket = stream_socket_client("ssl://$host:443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
if ($socket) {
$cert = stream_context_get_params($socket);
fclose($socket);
if (isset($cert['options']['ssl']['peer_certificate'])) {
$certInfo = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);
return [
'subject' => $certInfo['subject']['CN'] ?? '',
'issuer' => $certInfo['issuer']['CN'] ?? '',
'valid_from' => date('Y-m-d H:i:s', $certInfo['validFrom_time_t']),
'valid_to' => date('Y-m-d H:i:s', $certInfo['validTo_time_t']),
'days_remaining' => ceil(($certInfo['validTo_time_t'] - time()) / 86400)
];
}
}
return null;
}
/**
* SSL Configuration abrufen
*/
private function getSSLConfiguration()
{
return [
'force_https' => $this->config->get('FORCE_HTTPS', true),
'hsts_enabled' => $this->config->get('HSTS_ENABLED', true),
'hsts_max_age' => $this->config->get('HSTS_MAX_AGE', 31536000),
'secure_cookies' => $this->config->get('SECURE_COOKIES', true)
];
}
/**
* SSL Configuration aktualisieren
*/
private function updateSSLConfiguration()
{
$settings = [
'FORCE_HTTPS' => isset($_POST['force_https']),
'HSTS_ENABLED' => isset($_POST['hsts_enabled']),
'HSTS_MAX_AGE' => intval($_POST['hsts_max_age'] ?? 31536000),
'SECURE_COOKIES' => isset($_POST['secure_cookies'])
];
foreach ($settings as $key => $value) {
$this->config->set($key, $value);
}
$this->addFlashMessage('SSL/TLS-Konfiguration aktualisiert', 'success');
$this->redirect('/admin/security/ssl');
}
}