load_signatures(); $this->load_malware_hashes(); if ( ! WP_Security_Pack::get_setting( 'malware_scan_enabled', true ) ) { return; } // Schedule weekly scan. add_action( 'wpsp_weekly_malware_scan', array( $this, 'run_scheduled_scan' ) ); if ( ! wp_next_scheduled( 'wpsp_weekly_malware_scan' ) ) { wp_schedule_event( time(), 'weekly', 'wpsp_weekly_malware_scan' ); } } /** * Load known malware file hashes. * * Hashes can come from: * 1. Local database (user-added) * 2. Custom filter (wpsp_malware_hashes) */ private function load_malware_hashes() { // Load from database (user-added or previously downloaded). $this->malware_hashes = get_option( self::HASH_DB_OPTION, array() ); // Allow adding custom hashes via filter. $this->malware_hashes = apply_filters( 'wpsp_malware_hashes', $this->malware_hashes ); } /** * Add a hash to the local database. * * @param string $hash MD5 hash. * @param string $name Malware name/description. */ public function add_hash_to_database( $hash, $name ) { $hashes = get_option( self::HASH_DB_OPTION, array() ); $hashes[ strtolower( $hash ) ] = $name; update_option( self::HASH_DB_OPTION, $hashes ); $this->malware_hashes[ strtolower( $hash ) ] = $name; } /** * Remove a hash from the local database. * * @param string $hash MD5 hash. */ public function remove_hash_from_database( $hash ) { $hashes = get_option( self::HASH_DB_OPTION, array() ); $hash = strtolower( $hash ); if ( isset( $hashes[ $hash ] ) ) { unset( $hashes[ $hash ] ); update_option( self::HASH_DB_OPTION, $hashes ); } if ( isset( $this->malware_hashes[ $hash ] ) ) { unset( $this->malware_hashes[ $hash ] ); } } /** * Update malware hash database from remote source. * * @param string $source_url URL to fetch hashes from (JSON format). * @return bool|WP_Error */ public function update_hash_database( $source_url = '' ) { if ( empty( $source_url ) ) { // Default: Could be a GitHub raw URL or your own endpoint. // For now, just return - users can provide their own source. return new WP_Error( 'no_source', __( 'No hash database source URL provided.', 'wp-security-pack' ) ); } $response = wp_remote_get( $source_url, array( 'timeout' => 30 ) ); if ( is_wp_error( $response ) ) { return $response; } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( ! is_array( $data ) ) { return new WP_Error( 'invalid_data', __( 'Invalid hash database format.', 'wp-security-pack' ) ); } // Merge with existing hashes. $current_hashes = $this->malware_hashes; $new_hashes = array_merge( $current_hashes, $data ); update_option( self::HASH_DB_OPTION, $new_hashes ); update_option( self::HASH_DB_UPDATED_OPTION, time() ); $this->malware_hashes = $new_hashes; return true; } /** * Check if a file matches a known malware hash. * * @param string $file_path Path to file. * @return array|false Malware info if matched, false otherwise. */ public function check_file_hash( $file_path ) { if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) { return false; } $md5_hash = md5_file( $file_path ); if ( isset( $this->malware_hashes[ $md5_hash ] ) ) { return array( 'hash' => $md5_hash, 'name' => $this->malware_hashes[ $md5_hash ], 'method' => 'hash', ); } return false; } /** * Get hash database info. * * @return array */ public function get_hash_database_info() { return array( 'count' => count( $this->malware_hashes ), 'last_updated' => get_option( self::HASH_DB_UPDATED_OPTION, 0 ), 'source' => empty( get_option( self::HASH_DB_OPTION ) ) ? 'built-in' : 'updated', ); } /** * Load malware signatures. * * Patterns are organized by category and designed to minimize false positives * while catching real threats. We do NOT scan WordPress core files. */ private function load_signatures() { $this->signatures = array( // ===================================================================== // CRITICAL: Code Execution with Obfuscation // These patterns are almost always malicious. // ===================================================================== array( 'name' => 'Base64 Decode Execution', 'pattern' => '/\beval\s*\(\s*base64_decode\s*\(/i', 'severity' => 'critical', 'description' => 'Executing base64-encoded PHP code', ), array( 'name' => 'Gzinflate Execution', 'pattern' => '/\beval\s*\(\s*gzinflate\s*\(/i', 'severity' => 'critical', 'description' => 'Executing gzip-compressed PHP code', ), array( 'name' => 'Gzuncompress Execution', 'pattern' => '/\beval\s*\(\s*gzuncompress\s*\(/i', 'severity' => 'critical', 'description' => 'Executing compressed PHP code', ), array( 'name' => 'Str_rot13 Execution', 'pattern' => '/\beval\s*\(\s*str_rot13\s*\(/i', 'severity' => 'critical', 'description' => 'Executing ROT13-obfuscated PHP code', ), array( 'name' => 'Multiple Decode Layers', 'pattern' => '/base64_decode\s*\([^)]*base64_decode/is', 'severity' => 'critical', 'description' => 'Multiple layers of encoding (heavy obfuscation)', ), array( 'name' => 'Preg_replace /e Modifier', 'pattern' => '/preg_replace\s*\(\s*["\'][^"\']*\/[a-z]*e[a-z]*["\']/', 'severity' => 'critical', 'description' => 'Code execution via deprecated preg_replace /e modifier', ), // ===================================================================== // CRITICAL: User Input to Code Execution // Direct path from user input to code execution. // ===================================================================== array( 'name' => 'Eval with User Input', 'pattern' => '/\beval\s*\(\s*[\$\.\s]*\$_(POST|GET|REQUEST|COOKIE|SERVER|FILES)/i', 'severity' => 'critical', 'description' => 'Direct code execution from user input', ), array( 'name' => 'Assert with User Input', 'pattern' => '/\bassert\s*\(\s*[\$\.\s]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Code execution via assert() from user input', ), array( 'name' => 'Create_function with User Input', 'pattern' => '/\bcreate_function\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Dynamic function creation with user input', ), array( 'name' => 'Call_user_func with User Input', 'pattern' => '/\bcall_user_func(_array)?\s*\(\s*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Calling arbitrary function from user input', ), array( 'name' => 'Variable Function with User Input', 'pattern' => '/\$_(POST|GET|REQUEST|COOKIE)\s*\[[^\]]+\]\s*\(/i', 'severity' => 'critical', 'description' => 'Calling function name from user input', ), // ===================================================================== // CRITICAL: Shell Command Execution with User Input // ===================================================================== array( 'name' => 'Shell_exec with User Input', 'pattern' => '/\bshell_exec\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Shell command execution with user input', ), array( 'name' => 'System with User Input', 'pattern' => '/\bsystem\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'System command execution with user input', ), array( 'name' => 'Passthru with User Input', 'pattern' => '/\bpassthru\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Passthru command execution with user input', ), array( 'name' => 'Exec with User Input', 'pattern' => '/\bexec\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Exec command execution with user input', ), array( 'name' => 'Popen with User Input', 'pattern' => '/\bpopen\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Process opened with user-controlled command', ), array( 'name' => 'Proc_open with User Input', 'pattern' => '/\bproc_open\s*\([^)]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Process opened with user-controlled command', ), // ===================================================================== // CRITICAL: File Inclusion Vulnerabilities // ===================================================================== array( 'name' => 'Include with User Input', 'pattern' => '/\b(include|require|include_once|require_once)\s*\(?\s*[\$\.\s]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Local/Remote File Inclusion vulnerability', ), // ===================================================================== // CRITICAL: File Write Vulnerabilities // ===================================================================== array( 'name' => 'File_put_contents with User Input', 'pattern' => '/\bfile_put_contents\s*\([^,]*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Writing to user-controlled file path', ), array( 'name' => 'Fwrite with User Content', 'pattern' => '/\bfwrite\s*\([^,]+,\s*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'high', 'description' => 'Writing user content to file', ), array( 'name' => 'Fopen with User Path', 'pattern' => '/\bfopen\s*\(\s*\$_(POST|GET|REQUEST|COOKIE)/i', 'severity' => 'critical', 'description' => 'Opening user-controlled file path', ), array( 'name' => 'Unrestricted File Upload Path', 'pattern' => '/move_uploaded_file\s*\([^,]+,\s*[^)]*\$_(POST|GET|REQUEST)/i', 'severity' => 'critical', 'description' => 'Uploading file to user-controlled path', ), // ===================================================================== // HIGH: Known Backdoor Patterns // Specific patterns that indicate known malware structures. // ===================================================================== array( 'name' => 'Web Shell Upload Form', 'pattern' => '/