load_settings(); $this->ip_address = wpban_get_ip($this->settings['reverse_proxy'] ?? false); // Core hooks add_action('init', [$this, 'check_bypass'], 1); add_action('init', [$this, 'check_rate_limit'], 5); add_action('init', [$this, 'check_geo_block'], 6); add_action('init', [$this, 'check_ban'], 10); add_action('init', [$this, 'check_login_restriction'], 11); add_action('init', [$this, 'check_crawlers'], 12); add_action('wp_footer', [$this, 'check_browser_restrictions']); add_filter('robots_txt', [$this, 'modify_robots_txt'], 10, 2); // Login protection add_action('wp_login_failed', [$this, 'handle_login_failed']); add_filter('authenticate', [$this, 'check_login_attempt'], 30, 3); } private function load_settings() { $this->settings = wp_cache_get('settings', $this->cache_group); if ($this->settings === false) { $this->settings = get_option('wpban_settings', []); wp_cache_set('settings', $this->settings, $this->cache_group, 3600); } } public function check_bypass() { $bypass_path = get_option('wpban_bypass_path'); // URL bypass if (isset($_GET['wpban_bypass']) && $_GET['wpban_bypass'] === $bypass_path) { setcookie(WPBAN_BYPASS_KEY, $bypass_path, time() + DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true); $this->bypass_active = true; $this->log('bypass', 'bypass_activated'); wp_redirect(remove_query_arg('wpban_bypass')); exit; } // Cookie bypass if (isset($_COOKIE[WPBAN_BYPASS_KEY]) && $_COOKIE[WPBAN_BYPASS_KEY] === $bypass_path) { $this->bypass_active = true; } } public function check_rate_limit() { if ($this->bypass_active || empty($this->settings['rate_limits'])) { return; } global $wpdb; $table = $wpdb->prefix . 'wpban_rate_limits'; $limits = $this->settings['rate_limits']; // Determine action type $action = 'general'; if (strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false) { $action = 'login'; } elseif (strpos($_SERVER['REQUEST_URI'], '/wp-json/') !== false) { $action = 'api'; } // Get limit settings $window = 60; // seconds $max_requests = $limits['requests_per_minute'] ?? 60; if ($action === 'login') { $window = 3600; $max_requests = $limits['login_per_hour'] ?? 5; } elseif ($action === 'api') { $max_requests = $limits['api_per_minute'] ?? 30; } $window_start = date('Y-m-d H:i:s', time() - $window); // Check current count $current = $wpdb->get_row($wpdb->prepare( "SELECT count, window_start FROM $table WHERE ip = %s AND action = %s AND window_start > %s", $this->ip_address, $action, $window_start )); if ($current) { if ($current->count >= $max_requests) { $this->log('blocked', 'rate_limit', $action); $this->send_notification('rate_limit', [ 'action' => $action, 'count' => $current->count ]); wp_die(__('Too many requests. Please try again later.', 'wpban'), 429); } // Increment counter $wpdb->query($wpdb->prepare( "UPDATE $table SET count = count + 1, last_attempt = %s WHERE ip = %s AND action = %s", current_time('mysql'), $this->ip_address, $action )); } else { // Create new record $wpdb->insert($table, [ 'ip' => $this->ip_address, 'action' => $action, 'count' => 1, 'window_start' => current_time('mysql'), 'last_attempt' => current_time('mysql') ]); } } public function check_geo_block() { if ($this->bypass_active || empty($this->settings['geo_blocking']['enabled'])) { return; } $country = $this->get_country_code($this->ip_address); $blocked_countries = $this->settings['geo_blocking']['blocked_countries'] ?? []; $allowed_countries = $this->settings['geo_blocking']['allowed_countries'] ?? []; // If allowed list is set, only allow those countries if (!empty($allowed_countries) && !in_array($country, $allowed_countries)) { $this->log('blocked', 'geo_block', $country); $this->send_notification('geo_block', ['country' => $country]); wp_die(__('Access denied from your location.', 'wpban'), 403); } // Check blocked countries if (in_array($country, $blocked_countries)) { $this->log('blocked', 'geo_block', $country); $this->send_notification('geo_block', ['country' => $country]); wp_die(__('Access denied from your location.', 'wpban'), 403); } } public function check_ban() { if ($this->bypass_active) { return; } $checks = [ 'ip' => $this->is_ip_banned(), 'host' => $this->is_host_banned(), 'referer' => $this->is_referer_banned(), 'agent' => $this->is_agent_banned() ]; foreach ($checks as $type => $banned) { if ($banned && !$this->is_whitelisted()) { $this->log('banned', $type . '_ban'); $this->show_ban_message(); } } } public function check_login_restriction() { if ($this->bypass_active || empty($this->settings['login_allowed_ips'])) { return; } $login_files = ['wp-login.php', 'wp-register.php', 'xmlrpc.php']; $current_file = basename($_SERVER['SCRIPT_FILENAME']); if (in_array($current_file, $login_files)) { $allowed = false; foreach ($this->settings['login_allowed_ips'] as $pattern) { if (wpban_match_pattern($pattern, $this->ip_address)) { $allowed = true; break; } } if (!$allowed) { $this->log('blocked', 'login_restriction'); wp_redirect(home_url()); exit; } } } public function check_crawlers() { if ($this->bypass_active || empty($this->settings['blocked_crawlers'])) { return; } $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; foreach ($this->settings['blocked_crawlers'] as $crawler) { if (stripos($ua, $crawler) !== false) { $this->log('blocked', 'crawler_block', $crawler); http_response_code(403); exit('Access denied'); } } } public function check_browser_restrictions() { if ($this->bypass_active || empty($this->settings['browser_restrictions'])) { return; } $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; if (isset($this->settings['browser_restrictions']['wechat_qq']) && $this->settings['browser_restrictions']['wechat_qq']['enabled'] && (strpos($ua, 'MQQBrowser') !== false || strpos($ua, 'MicroMessenger') !== false)) { $this->log('blocked', 'browser_restriction', 'WeChat/QQ'); $this->show_browser_block_message($this->settings['browser_restrictions']['wechat_qq']); } } public function handle_login_failed($username) { $this->log('failed_login', 'login_attempt', $username); // Check for brute force global $wpdb; $table = $wpdb->prefix . 'wpban_logs'; $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE ip = %s AND action = 'failed_login' AND timestamp > %s", $this->ip_address, date('Y-m-d H:i:s', strtotime('-1 hour')) )); if ($count >= 10) { $this->send_notification('brute_force', [ 'attempts' => $count, 'username' => $username ]); } } public function check_login_attempt($user, $username, $password) { if (empty($username) || empty($password)) { return $user; } // Additional login security checks can be added here return $user; } public function modify_robots_txt($output, $public) { if (!empty($this->settings['blocked_crawlers'])) { $output .= "\n# WPBan Security Rules\n"; foreach ($this->settings['blocked_crawlers'] as $crawler) { $output .= "User-agent: $crawler\n"; $output .= "Disallow: /\n\n"; } } return $output; } // Helper methods private function is_ip_banned() { foreach ($this->settings['banned_ips'] ?? [] as $pattern) { if (wpban_match_pattern($pattern, $this->ip_address)) { return true; } } foreach ($this->settings['banned_ranges'] ?? [] as $range) { if (wpban_ip_in_range($this->ip_address, $range)) { return true; } } return false; } private function is_host_banned() { if (empty($this->settings['banned_hosts'])) { return false; } $hostname = $this->get_cached_data('hostname_' . $this->ip_address, function() { return gethostbyaddr($this->ip_address); }, 3600); foreach ($this->settings['banned_hosts'] as $pattern) { if (wpban_match_pattern($pattern, $hostname)) { return true; } } return false; } private function is_referer_banned() { $referer = $_SERVER['HTTP_REFERER'] ?? ''; if (empty($referer) || empty($this->settings['banned_referers'])) { return false; } foreach ($this->settings['banned_referers'] as $pattern) { if (wpban_match_pattern($pattern, $referer)) { return true; } } return false; } private function is_agent_banned() { $agent = $_SERVER['HTTP_USER_AGENT'] ?? ''; if (empty($agent) || empty($this->settings['banned_agents'])) { return false; } foreach ($this->settings['banned_agents'] as $pattern) { if (wpban_match_pattern($pattern, $agent)) { return true; } } return false; } private function is_whitelisted() { foreach ($this->settings['whitelist_ips'] ?? [] as $pattern) { if (wpban_match_pattern($pattern, $this->ip_address) || wpban_ip_in_range($this->ip_address, $pattern)) { return true; } } return false; } private function get_country_code($ip) { global $wpdb; $table = $wpdb->prefix . 'wpban_geo_cache'; // Check cache first $cached = $wpdb->get_var($wpdb->prepare( "SELECT country_code FROM $table WHERE ip = %s", $ip )); if ($cached) { return $cached; } // Use IP geolocation service $country = $this->lookup_country($ip); // Cache result if ($country) { $wpdb->replace($table, [ 'ip' => $ip, 'country_code' => $country['code'], 'country_name' => $country['name'], 'city' => $country['city'] ?? null, 'cached_at' => current_time('mysql') ]); } return $country['code'] ?? 'XX'; } private function lookup_country($ip) { // Try multiple services $services = [ 'ipapi' => "http://ip-api.com/json/{$ip}?fields=status,country,countryCode,city", 'ipinfo' => "https://ipinfo.io/{$ip}/json" ]; foreach ($services as $name => $url) { $response = wp_remote_get($url, ['timeout' => 2]); if (!is_wp_error($response)) { $data = json_decode(wp_remote_retrieve_body($response), true); if ($name === 'ipapi' && $data['status'] === 'success') { return [ 'code' => $data['countryCode'], 'name' => $data['country'], 'city' => $data['city'] ]; } elseif ($name === 'ipinfo' && isset($data['country'])) { return [ 'code' => $data['country'], 'name' => $data['country'], 'city' => $data['city'] ?? null ]; } } } return null; } private function log($action, $reason, $details = '') { if (empty($this->settings['enable_logging'])) { return; } global $wpdb; $country = $action === 'geo_block' ? $details : $this->get_country_code($this->ip_address); $wpdb->insert($wpdb->prefix . 'wpban_logs', [ 'ip' => $this->ip_address, 'country_code' => $country, 'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500), 'referer' => substr($_SERVER['HTTP_REFERER'] ?? '', 0, 500), 'uri' => substr($_SERVER['REQUEST_URI'] ?? '', 0, 500), 'action' => $action, 'reason' => $reason . ($details ? ' - ' . $details : ''), 'timestamp' => current_time('mysql') ]); } private function send_notification($type, $data = []) { if (empty($this->settings['email_notifications']['enabled']) || !in_array($type, $this->settings['email_notifications']['events'] ?? [])) { return; } // Check threshold global $wpdb; $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}wpban_logs WHERE ip = %s AND timestamp > %s", $this->ip_address, date('Y-m-d H:i:s', strtotime('-1 hour')) )); if ($count < ($this->settings['email_notifications']['threshold'] ?? 10)) { return; } // Send email $to = $this->settings['email_notifications']['recipient']; $subject = sprintf(__('[%s] Security Alert: %s', 'wpban'), get_bloginfo('name'), $type); $message = $this->format_notification_email($type, $data); wp_mail($to, $subject, $message); } private function format_notification_email($type, $data) { $site = get_bloginfo('name'); $time = current_time('mysql'); $message = "Security alert on {$site}\n\n"; $message .= "Event: {$type}\n"; $message .= "Time: {$time}\n"; $message .= "IP Address: {$this->ip_address}\n"; $message .= "Country: " . $this->get_country_code($this->ip_address) . "\n"; if (!empty($data)) { $message .= "\nDetails:\n"; foreach ($data as $key => $value) { $message .= "- {$key}: {$value}\n"; } } $message .= "\nView logs: " . admin_url('admin.php?page=wpban-logs'); return $message; } private function show_ban_message() { $message = $this->settings['ban_message'] ?? '