'Failed Login Attempt', 'suspicious_activity' => 'Suspicious Activity', 'privilege_escalation' => 'Privilege Escalation Attempt', 'file_modification' => 'Unauthorized File Modification', 'sql_injection' => 'SQL Injection Attempt', 'xss_attempt' => 'Cross-Site Scripting Attempt', 'brute_force' => 'Brute Force Attack', 'admin_access' => 'Unauthorized Admin Access' ]; /** * Threat levels */ const THREAT_LOW = 'low'; const THREAT_MEDIUM = 'medium'; const THREAT_HIGH = 'high'; const THREAT_CRITICAL = 'critical'; /** * Security settings */ private static $settings = [ 'max_failed_logins' => 5, 'lockout_duration' => 900, // 15 minutes 'monitor_file_changes' => true, 'scan_requests' => true, 'alert_threshold' => 3, 'auto_block_ips' => true ]; /** * Blocked IPs cache */ private static $blocked_ips = []; /** * Initialize security monitoring */ public static function init() { // Load settings self::$settings = array_merge(self::$settings, get_option('hvac_security_settings', [])); self::$blocked_ips = get_option('hvac_blocked_ips', []); // Security monitoring hooks add_action('wp_login_failed', [__CLASS__, 'handle_failed_login']); add_action('wp_login', [__CLASS__, 'handle_successful_login'], 10, 2); add_action('init', [__CLASS__, 'check_request_security']); add_action('admin_init', [__CLASS__, 'monitor_admin_access']); // File monitoring if (self::$settings['monitor_file_changes']) { add_action('wp_loaded', [__CLASS__, 'monitor_file_integrity']); } // Database monitoring add_filter('query', [__CLASS__, 'monitor_database_queries']); // Admin interface if (is_admin()) { add_action('admin_menu', [__CLASS__, 'add_admin_menu']); add_action('wp_ajax_hvac_security_action', [__CLASS__, 'handle_security_action']); } // REST API for external monitoring add_action('rest_api_init', [__CLASS__, 'register_rest_endpoints']); // Cleanup old security events add_action('wp_scheduled_delete', [__CLASS__, 'cleanup_old_events']); // Emergency lockdown capability add_action('hvac_emergency_lockdown', [__CLASS__, 'emergency_lockdown']); // WP-CLI integration if (defined('WP_CLI') && WP_CLI) { WP_CLI::add_command('hvac security', [__CLASS__, 'wp_cli_security']); } } /** * Handle failed login attempts */ public static function handle_failed_login($username) { $ip = self::get_client_ip(); // Record the failed attempt self::log_security_event('failed_login', self::THREAT_MEDIUM, [ 'username' => $username, 'ip_address' => $ip, 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'timestamp' => time() ]); // Check for brute force pattern $recent_attempts = self::get_recent_events('failed_login', $ip, 3600); // Last hour if (count($recent_attempts) >= self::$settings['max_failed_logins']) { // Brute force detected self::log_security_event('brute_force', self::THREAT_HIGH, [ 'ip_address' => $ip, 'attempts' => count($recent_attempts), 'usernames' => array_unique(array_column($recent_attempts, 'username')), 'auto_blocked' => self::$settings['auto_block_ips'] ]); if (self::$settings['auto_block_ips']) { self::block_ip($ip, 'Brute force attack detected'); } // Send immediate alert self::send_security_alert('Brute Force Attack', [ 'ip_address' => $ip, 'attempts' => count($recent_attempts), 'action_taken' => self::$settings['auto_block_ips'] ? 'IP blocked' : 'Logged only' ]); } } /** * Handle successful login */ public static function handle_successful_login($username, $user) { $ip = self::get_client_ip(); // Check if this is a suspicious login $is_suspicious = false; $reasons = []; // Check for unusual location (simplified - could integrate with GeoIP) $user_last_ip = get_user_meta($user->ID, 'hvac_last_login_ip', true); if ($user_last_ip && $user_last_ip !== $ip) { $is_suspicious = true; $reasons[] = 'Different IP address'; } // Check for admin role login if (user_can($user, 'manage_options')) { self::log_security_event('admin_access', self::THREAT_LOW, [ 'username' => $username, 'ip_address' => $ip, 'user_id' => $user->ID, 'suspicious' => $is_suspicious, 'reasons' => $reasons ]); if ($is_suspicious) { self::send_security_alert('Suspicious Admin Login', [ 'username' => $username, 'ip_address' => $ip, 'reasons' => $reasons ]); } } // Update user's last login IP update_user_meta($user->ID, 'hvac_last_login_ip', $ip); update_user_meta($user->ID, 'hvac_last_login_time', time()); } /** * Check request security */ public static function check_request_security() { // Skip checks for admin, CLI, or cron if (is_admin() || wp_doing_cron() || (defined('WP_CLI') && WP_CLI)) { return; } $ip = self::get_client_ip(); // Check if IP is blocked if (self::is_ip_blocked($ip)) { self::block_request('IP address is blocked'); return; } if (!self::$settings['scan_requests']) { return; } $request_data = $_REQUEST; $threat_level = self::THREAT_LOW; $threats_detected = []; // Check for SQL injection patterns $sql_patterns = [ '/union.*select/i', '/drop.*table/i', '/insert.*into/i', '/delete.*from/i', '/update.*set/i', '/exec\s*\(/i' ]; foreach ($request_data as $key => $value) { if (is_string($value)) { foreach ($sql_patterns as $pattern) { if (preg_match($pattern, $value)) { $threats_detected[] = 'SQL injection pattern in: ' . $key; $threat_level = self::THREAT_HIGH; } } } } // Check for XSS patterns $xss_patterns = [ '/.*?<\/script>/i', '/javascript:/i', '/onload\s*=/i', '/onerror\s*=/i', '//i' ]; foreach ($request_data as $key => $value) { if (is_string($value)) { foreach ($xss_patterns as $pattern) { if (preg_match($pattern, $value)) { $threats_detected[] = 'XSS pattern in: ' . $key; if ($threat_level === self::THREAT_LOW) { $threat_level = self::THREAT_MEDIUM; } } } } } // Check for file inclusion attempts $file_patterns = [ '/\.\.\//i', '/etc\/passwd/i', '/proc\/.*?/i', '/boot\.ini/i' ]; foreach ($request_data as $key => $value) { if (is_string($value)) { foreach ($file_patterns as $pattern) { if (preg_match($pattern, $value)) { $threats_detected[] = 'File inclusion attempt in: ' . $key; $threat_level = self::THREAT_HIGH; } } } } // Log and respond to threats if (!empty($threats_detected)) { $event_type = strpos(implode(' ', $threats_detected), 'SQL') !== false ? 'sql_injection' : 'xss_attempt'; self::log_security_event($event_type, $threat_level, [ 'ip_address' => $ip, 'threats' => $threats_detected, 'request_uri' => $_SERVER['REQUEST_URI'] ?? '', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'request_data' => $request_data ]); // Auto-block for high/critical threats if ($threat_level === self::THREAT_HIGH || $threat_level === self::THREAT_CRITICAL) { if (self::$settings['auto_block_ips']) { self::block_ip($ip, 'Malicious request detected: ' . implode(', ', $threats_detected)); } self::send_security_alert('Malicious Request Blocked', [ 'ip_address' => $ip, 'threats' => $threats_detected, 'request_uri' => $_SERVER['REQUEST_URI'] ?? '' ]); self::block_request('Malicious request detected'); } } } /** * Monitor admin access */ public static function monitor_admin_access() { if (!current_user_can('manage_options')) { return; } $user = wp_get_current_user(); $ip = self::get_client_ip(); // Check for privilege escalation attempts if (isset($_POST['action']) && $_POST['action'] === 'update' && isset($_POST['users'])) { self::log_security_event('privilege_escalation', self::THREAT_MEDIUM, [ 'user_id' => $user->ID, 'username' => $user->user_login, 'ip_address' => $ip, 'action' => 'User role modification attempt' ]); } // Monitor plugin/theme installations if (isset($_REQUEST['action']) && in_array($_REQUEST['action'], ['install-plugin', 'install-theme', 'upload-plugin', 'upload-theme'])) { self::log_security_event('suspicious_activity', self::THREAT_LOW, [ 'user_id' => $user->ID, 'username' => $user->user_login, 'ip_address' => $ip, 'action' => $_REQUEST['action'], 'item' => $_REQUEST['plugin'] ?? $_REQUEST['theme'] ?? 'unknown' ]); } } /** * Monitor file integrity */ public static function monitor_file_integrity() { // Only run this check periodically to avoid performance issues $last_check = get_option('hvac_last_file_check', 0); if (time() - $last_check < 3600) { // Check every hour return; } update_option('hvac_last_file_check', time()); // Check core plugin files $critical_files = [ HVAC_PLUGIN_FILE, HVAC_PLUGIN_DIR . 'includes/class-hvac-plugin.php', HVAC_PLUGIN_DIR . 'includes/class-hvac-community-events.php' ]; $stored_hashes = get_option('hvac_file_hashes', []); $current_hashes = []; $modified_files = []; foreach ($critical_files as $file) { if (file_exists($file)) { $current_hash = md5_file($file); $current_hashes[basename($file)] = $current_hash; $stored_hash = $stored_hashes[basename($file)] ?? null; if ($stored_hash && $stored_hash !== $current_hash) { $modified_files[] = basename($file); } } } // Update stored hashes if (empty($stored_hashes)) { // First run - just store hashes update_option('hvac_file_hashes', $current_hashes); } else { // Report modifications if (!empty($modified_files)) { self::log_security_event('file_modification', self::THREAT_HIGH, [ 'modified_files' => $modified_files, 'ip_address' => self::get_client_ip(), 'detection_time' => time() ]); self::send_security_alert('File Modification Detected', [ 'modified_files' => $modified_files, 'total_files' => count($modified_files) ]); } update_option('hvac_file_hashes', $current_hashes); } } /** * Monitor database queries */ public static function monitor_database_queries($query) { // Skip monitoring for admin area and known safe contexts if (is_admin() || wp_doing_cron() || (defined('WP_CLI') && WP_CLI)) { return $query; } // Check for suspicious patterns $suspicious_patterns = [ '/UNION.*SELECT/i', '/DROP\s+TABLE/i', '/DELETE.*FROM.*WHERE.*1\s*=\s*1/i', '/UPDATE.*SET.*WHERE.*1\s*=\s*1/i' ]; foreach ($suspicious_patterns as $pattern) { if (preg_match($pattern, $query)) { self::log_security_event('sql_injection', self::THREAT_CRITICAL, [ 'query_pattern' => preg_replace('/\s+/', ' ', substr($query, 0, 200)), 'ip_address' => self::get_client_ip(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'request_uri' => $_SERVER['REQUEST_URI'] ?? '' ]); // Block immediately for critical SQL injection attempts self::send_security_alert('Critical SQL Injection Attempt', [ 'ip_address' => self::get_client_ip(), 'query_sample' => substr($query, 0, 100) ]); if (self::$settings['auto_block_ips']) { self::block_ip(self::get_client_ip(), 'SQL injection attempt detected'); self::block_request('Malicious database query detected'); } break; } } return $query; } /** * Log security event */ private static function log_security_event($type, $threat_level, $data) { global $wpdb; $event = [ 'type' => $type, 'threat_level' => $threat_level, 'ip_address' => $data['ip_address'] ?? self::get_client_ip(), 'user_id' => get_current_user_id(), 'data' => json_encode($data), 'timestamp' => time() ]; // Store in options table (or create custom table for high-volume sites) $events = get_option('hvac_security_events', []); $events[] = $event; // Keep only last 1000 events to prevent database bloat if (count($events) > 1000) { $events = array_slice($events, -1000); } update_option('hvac_security_events', $events); // Log to WordPress error log as well HVAC_Logger::warning( "Security event: $type ($threat_level) - " . json_encode($data), 'Security Monitor' ); // Check if alert threshold is reached $recent_high_threats = self::count_recent_threats([self::THREAT_HIGH, self::THREAT_CRITICAL], 3600); if ($recent_high_threats >= self::$settings['alert_threshold']) { self::send_security_alert('Security Alert Threshold Reached', [ 'recent_threats' => $recent_high_threats, 'threshold' => self::$settings['alert_threshold'], 'time_window' => '1 hour' ]); } } /** * Get recent security events */ private static function get_recent_events($type, $ip = null, $timeframe = 3600) { $events = get_option('hvac_security_events', []); $cutoff_time = time() - $timeframe; return array_filter($events, function($event) use ($type, $ip, $cutoff_time) { if ($event['timestamp'] < $cutoff_time) { return false; } if ($event['type'] !== $type) { return false; } if ($ip && $event['ip_address'] !== $ip) { return false; } return true; }); } /** * Count recent threats by level */ private static function count_recent_threats($threat_levels, $timeframe = 3600) { $events = get_option('hvac_security_events', []); $cutoff_time = time() - $timeframe; return count(array_filter($events, function($event) use ($threat_levels, $cutoff_time) { return $event['timestamp'] >= $cutoff_time && in_array($event['threat_level'], $threat_levels); })); } /** * Block IP address */ private static function block_ip($ip, $reason) { self::$blocked_ips[$ip] = [ 'reason' => $reason, 'timestamp' => time(), 'expires' => time() + self::$settings['lockout_duration'] ]; update_option('hvac_blocked_ips', self::$blocked_ips); HVAC_Logger::warning("IP blocked: $ip - $reason", 'Security Monitor'); } /** * Check if IP is blocked */ private static function is_ip_blocked($ip) { if (!isset(self::$blocked_ips[$ip])) { return false; } $block_info = self::$blocked_ips[$ip]; if (time() > $block_info['expires']) { // Block expired, remove it unset(self::$blocked_ips[$ip]); update_option('hvac_blocked_ips', self::$blocked_ips); return false; } return true; } /** * Block current request */ private static function block_request($reason) { http_response_code(403); die('Access Denied: ' . $reason); } /** * Get client IP address */ private static function get_client_ip() { $headers = [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' ]; foreach ($headers as $header) { if (!empty($_SERVER[$header])) { $ips = explode(',', $_SERVER[$header]); $ip = trim($ips[0]); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { return $ip; } } } return $_SERVER['REMOTE_ADDR'] ?? 'unknown'; } /** * Send security alert */ private static function send_security_alert($subject, $data) { $admin_email = get_option('admin_email'); $site_name = get_bloginfo('name'); $message = "Security Alert from $site_name\n\n"; $message .= "Alert: $subject\n\n"; foreach ($data as $key => $value) { if (is_array($value)) { $value = implode(', ', $value); } $message .= ucfirst(str_replace('_', ' ', $key)) . ": $value\n"; } $message .= "\nTime: " . date('Y-m-d H:i:s') . "\n"; $message .= "Check the security monitor for more details."; wp_mail($admin_email, "[$site_name] $subject", $message); } /** * Emergency lockdown */ public static function emergency_lockdown() { // Block all non-admin access update_option('hvac_emergency_lockdown', [ 'enabled' => true, 'timestamp' => time(), 'triggered_by' => get_current_user_id() ]); // Send emergency notification self::send_security_alert('Emergency Lockdown Activated', [ 'triggered_by' => get_current_user_id(), 'timestamp' => time(), 'action' => 'All non-admin access blocked' ]); HVAC_Logger::error('Emergency lockdown activated', 'Security Monitor'); } /** * Get security statistics */ public static function get_security_stats() { $events = get_option('hvac_security_events', []); $blocked_ips = get_option('hvac_blocked_ips', []); // Count events by type and threat level $stats = [ 'total_events' => count($events), 'blocked_ips' => count($blocked_ips), 'events_by_type' => [], 'events_by_threat' => [], 'recent_events' => count(array_filter($events, function($event) { return $event['timestamp'] >= (time() - 86400); // Last 24 hours })) ]; foreach ($events as $event) { $type = $event['type']; $threat = $event['threat_level']; $stats['events_by_type'][$type] = ($stats['events_by_type'][$type] ?? 0) + 1; $stats['events_by_threat'][$threat] = ($stats['events_by_threat'][$threat] ?? 0) + 1; } return $stats; } /** * Cleanup old security events */ public static function cleanup_old_events() { // Remove events older than 30 days $events = get_option('hvac_security_events', []); $cutoff_time = time() - (30 * 86400); $events = array_filter($events, function($event) use ($cutoff_time) { return $event['timestamp'] >= $cutoff_time; }); update_option('hvac_security_events', array_values($events)); // Clean up expired IP blocks $blocked_ips = get_option('hvac_blocked_ips', []); $current_time = time(); $updated = false; foreach ($blocked_ips as $ip => $block_info) { if ($current_time > $block_info['expires']) { unset($blocked_ips[$ip]); $updated = true; } } if ($updated) { update_option('hvac_blocked_ips', $blocked_ips); } } /** * Add admin menu */ public static function add_admin_menu() { if (current_user_can('manage_options')) { add_management_page( 'HVAC Security Monitor', 'HVAC Security', 'manage_options', 'hvac-security-monitor', [__CLASS__, 'admin_page'] ); } } /** * Admin page */ public static function admin_page() { $stats = self::get_security_stats(); $recent_events = array_slice(get_option('hvac_security_events', []), -20, 20, true); $blocked_ips = get_option('hvac_blocked_ips', []); ?>

HVAC Security Monitor

Security Overview

Total Events:

Recent Events (24h):

Blocked IPs:

Threat Distribution

$count): ?>

:

Recent Security Events

Time Type Threat Level IP Address Details
No recent security events
View Details

Blocked IP Addresses

$block_info): ?>
IP Address Reason Blocked At Expires Action
No blocked IPs
'GET', 'callback' => [__CLASS__, 'rest_security_stats'], 'permission_callback' => function() { return current_user_can('manage_options'); } ]); } /** * REST API security stats */ public static function rest_security_stats() { $stats = self::get_security_stats(); return new WP_REST_Response([ 'stats' => $stats, 'timestamp' => time() ], 200); } /** * WP-CLI security command */ public static function wp_cli_security($args, $assoc_args) { $subcommand = $args[0] ?? 'stats'; switch ($subcommand) { case 'stats': $stats = self::get_security_stats(); WP_CLI::line('HVAC Security Statistics:'); WP_CLI::line('Total Events: ' . $stats['total_events']); WP_CLI::line('Recent Events (24h): ' . $stats['recent_events']); WP_CLI::line('Blocked IPs: ' . $stats['blocked_ips']); break; case 'events': $events = array_slice(get_option('hvac_security_events', []), -10); WP_CLI::line('Recent Security Events:'); foreach ($events as $event) { WP_CLI::line(sprintf( '%s - %s (%s) - %s', date('Y-m-d H:i:s', $event['timestamp']), $event['type'], $event['threat_level'], $event['ip_address'] )); } break; default: WP_CLI::error('Unknown subcommand. Use: stats, events'); } } }