Add complete enterprise-level reliability, security, and performance systems: ## Core Monitoring Systems - **Health Monitor**: 8 automated health checks with email alerts and REST API - **Error Recovery**: 4 recovery strategies (retry, fallback, circuit breaker, graceful failure) - **Security Monitor**: Real-time threat detection with automatic IP blocking - **Performance Monitor**: Performance tracking with automated benchmarks and alerts ## Data Protection & Optimization - **Backup Manager**: Automated backups with encryption, compression, and disaster recovery - **Cache Optimizer**: Intelligent caching with 3 strategies and 5 specialized cache groups ## Enterprise Features - Automated scheduling with WordPress cron integration - Admin dashboards for all systems under Tools menu - REST API endpoints for external monitoring - WP-CLI commands for automation and CI/CD - Comprehensive documentation (docs/MONITORING-SYSTEMS.md) - Emergency response systems with immediate email alerts - Circuit breaker pattern for external service failures - Smart cache warming and invalidation - Database query caching and optimization - File integrity monitoring - Performance degradation detection ## Integration - Plugin architecture updated with proper initialization - Singleton pattern for all monitoring classes - WordPress hooks and filters integration - Background job processing system - Comprehensive error handling and logging Systems provide enterprise-grade reliability with automated threat response, proactive performance monitoring, and complete disaster recovery capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
923 lines
No EOL
32 KiB
PHP
923 lines
No EOL
32 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Security Monitor
|
|
*
|
|
* Provides real-time security monitoring, threat detection, and automated
|
|
* security response for the HVAC Community Events plugin
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.8
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* HVAC_Security_Monitor class
|
|
*/
|
|
class HVAC_Security_Monitor {
|
|
|
|
/**
|
|
* Security event types
|
|
*/
|
|
const EVENT_TYPES = [
|
|
'failed_login' => '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.*?>.*?<\/script>/i',
|
|
'/javascript:/i',
|
|
'/onload\s*=/i',
|
|
'/onerror\s*=/i',
|
|
'/<iframe.*?>/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', []);
|
|
|
|
?>
|
|
<div class="wrap">
|
|
<h1>HVAC Security Monitor</h1>
|
|
|
|
<div class="security-stats">
|
|
<div class="card">
|
|
<h2>Security Overview</h2>
|
|
<p><strong>Total Events:</strong> <?php echo $stats['total_events']; ?></p>
|
|
<p><strong>Recent Events (24h):</strong> <?php echo $stats['recent_events']; ?></p>
|
|
<p><strong>Blocked IPs:</strong> <?php echo $stats['blocked_ips']; ?></p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Threat Distribution</h2>
|
|
<?php foreach ($stats['events_by_threat'] as $threat => $count): ?>
|
|
<p><strong><?php echo ucfirst($threat); ?>:</strong> <?php echo $count; ?></p>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Recent Security Events</h2>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Type</th>
|
|
<th>Threat Level</th>
|
|
<th>IP Address</th>
|
|
<th>Details</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($recent_events)): ?>
|
|
<tr>
|
|
<td colspan="5">No recent security events</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach (array_reverse($recent_events) as $event): ?>
|
|
<tr class="threat-<?php echo esc_attr($event['threat_level']); ?>">
|
|
<td><?php echo date('Y-m-d H:i:s', $event['timestamp']); ?></td>
|
|
<td><?php echo esc_html(self::EVENT_TYPES[$event['type']] ?? $event['type']); ?></td>
|
|
<td><?php echo esc_html(strtoupper($event['threat_level'])); ?></td>
|
|
<td><?php echo esc_html($event['ip_address']); ?></td>
|
|
<td>
|
|
<details>
|
|
<summary>View Details</summary>
|
|
<pre><?php echo esc_html(json_encode(json_decode($event['data']), JSON_PRETTY_PRINT)); ?></pre>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Blocked IP Addresses</h2>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>IP Address</th>
|
|
<th>Reason</th>
|
|
<th>Blocked At</th>
|
|
<th>Expires</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($blocked_ips)): ?>
|
|
<tr>
|
|
<td colspan="5">No blocked IPs</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($blocked_ips as $ip => $block_info): ?>
|
|
<tr>
|
|
<td><?php echo esc_html($ip); ?></td>
|
|
<td><?php echo esc_html($block_info['reason']); ?></td>
|
|
<td><?php echo date('Y-m-d H:i:s', $block_info['timestamp']); ?></td>
|
|
<td><?php echo date('Y-m-d H:i:s', $block_info['expires']); ?></td>
|
|
<td>
|
|
<button type="button" class="button button-small unblock-ip"
|
|
data-ip="<?php echo esc_attr($ip); ?>">
|
|
Unblock
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<style>
|
|
.threat-low { background-color: #d4edda; }
|
|
.threat-medium { background-color: #fff3cd; }
|
|
.threat-high { background-color: #f8d7da; }
|
|
.threat-critical { background-color: #d1ecf1; }
|
|
.security-stats { display: flex; gap: 20px; }
|
|
.security-stats .card { flex: 1; }
|
|
</style>
|
|
|
|
<script>
|
|
document.querySelectorAll('.unblock-ip').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const ip = this.dataset.ip;
|
|
if (confirm(`Unblock IP address ${ip}?`)) {
|
|
fetch(ajaxurl, {
|
|
method: 'POST',
|
|
body: new URLSearchParams({
|
|
action: 'hvac_security_action',
|
|
security_action: 'unblock_ip',
|
|
ip_address: ip,
|
|
nonce: '<?php echo wp_create_nonce('hvac_security_action'); ?>'
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Failed to unblock IP');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Handle security actions
|
|
*/
|
|
public static function handle_security_action() {
|
|
check_ajax_referer('hvac_security_action', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
}
|
|
|
|
$action = sanitize_text_field($_POST['security_action']);
|
|
|
|
switch ($action) {
|
|
case 'unblock_ip':
|
|
$ip = sanitize_text_field($_POST['ip_address']);
|
|
$blocked_ips = get_option('hvac_blocked_ips', []);
|
|
unset($blocked_ips[$ip]);
|
|
update_option('hvac_blocked_ips', $blocked_ips);
|
|
wp_send_json_success('IP unblocked');
|
|
break;
|
|
|
|
default:
|
|
wp_send_json_error('Unknown action');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register REST endpoints
|
|
*/
|
|
public static function register_rest_endpoints() {
|
|
register_rest_route('hvac/v1', '/security/stats', [
|
|
'methods' => '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');
|
|
}
|
|
}
|
|
}
|