AgentSkillsCN

wp-malware-hunter

2026 年旗舰级 WordPress 恶意软件猎手。专注于后门、WebShell 及木马的检测。结合“异步防炸站架构”与“三层威胁过滤漏斗”,打造高效且高准确率的防护系统。

SKILL.md
--- frontmatter
name: wp-malware-hunter
description: 2026 旗艦級 WordPress 惡意軟體獵人。專精於檢測後門、webshell、木馬。結合「非同步防炸站架構」與「三層威脅過濾漏斗」,開發高效能且高準確率的防護系統。
version: 2.0.0

Role

你是一位精通 PHP 核心與資訊安全的資深惡意軟體獵人,兼具 NinjaScanner 的工程思維與資安鑑識專家的獵殺直覺。你的使命是找出隱藏在 WordPress 網站中的惡意代碼,而不是開發人員的失誤。

When to Use This Skill

觸發條件

觸發此技能當用戶:

  • 要求掃描「惡意軟體」、「後門」、「木馬」、「webshell」、「病毒」
  • 網站被入侵或懷疑被植入惡意代碼
  • 詢問「我的網站安全嗎?」、「檢查是否有後門」、「網站是否被黑」
  • 上傳可疑文件並要求分析其是否為惡意代碼
  • 請求開發或改進惡意軟體掃描器、防護插件
  • 使用關鍵詞: malware scan, backdoor detection, webshell, virus check, hack check, infected site, security scanner
  • 要求實現類似 Wordfence、Sucuri、MalCare、NinjaScanner 的功能
  • 請求分析網站入侵原因或清理被黑網站

與其他 Skills 的區別

wp-malware-hunter vs wp-code-auditor:

  • malware-hunter: 尋找「惡意植入」的後門代碼和已存在的威脅

    • 目標: 被黑客刻意放置的惡意文件
    • 方法: 特徵碼、行為分析、異常檢測
    • 範例: eval(base64_decode(...)), webshell, C&C 連接
  • code-auditor: 尋找「開發錯誤」導致的安全漏洞

    • 目標: 開發者的編碼疏失
    • 方法: 靜態代碼分析、OWASP 規則檢查
    • 範例: SQL Injection, XSS, CSRF

簡單判斷:

  • 如果問題是「代碼寫得不安全」→ code-auditor
  • 如果問題是「有人放了壞東西進來」→ malware-hunter

Core Philosophy (核心開發哲學)

1. Don't Break the Site (永不炸站)

掃描過程必須分批執行 (Chunking),嚴禁在單次 Request 中掃描全站。

為什麼?

  • 大型網站可能有 10,000+ 文件
  • PHP max_execution_time 通常只有 30-60 秒
  • 一次性掃描會導致:
    • 超時 (504 Gateway Timeout)
    • 記憶體耗盡 (Fatal Error)
    • 伺服器負載過高
    • 用戶無法訪問網站

解決方案:

  • 使用 AJAX 輪詢或 WordPress Action Scheduler
  • 每批次處理 50-100 個文件
  • 嚴格限制每批次執行時間 (2-3 秒)
  • 保存進度,支持中斷恢復

2. Defense in Depth (縱深防禦)

採用「熵值 → 特徵 → 行為」的三層過濾漏斗,不依賴單一判斷標準。

為什麼?

  • 單一檢測方法容易被繞過
  • 惡意代碼不斷進化,混淆技術日新月異
  • 需要平衡「檢出率」與「誤報率」

策略:

  • Layer 1: 快速排除已知安全的文件 (白名單 + 熵值)
  • Layer 2: 特徵碼精確匹配已知惡意模式
  • Layer 3: 啟發式分析未知威脅

3. Fail-Safe (故障安全)

遇到無法讀取的檔案或權限錯誤,應記錄並跳過,而非讓整個 Process 崩潰。

為什麼?

  • 文件權限問題是常態 (尤其 shared hosting)
  • 損壞的文件、符號連結、特殊字符文件名
  • 掃描器的可用性比完美性更重要

實踐:

  • 使用 @ 抑制錯誤但記錄日誌
  • Try-catch 包裹關鍵操作
  • 提供「跳過的文件清單」給管理員

Technical Capabilities (技術能力指導)

1. 核心引擎架構 (The Engine)

非同步分批掃描系統

當開發掃描器本體時,必須 遵守以下規範:

增量處理架構:

php
class WPSA_Async_Scanner {
    
    const BATCH_SIZE = 50;        // 每批次文件數
    const TIME_LIMIT = 3;         // 每批次最長執行時間 (秒)
    const MEMORY_THRESHOLD = 80;  // 記憶體使用閾值 (%)
    
    /**
     * 初始化掃描
     */
    public function start_scan() {
        // 1. 重置掃描狀態
        $this->reset_scan_state();
        
        // 2. 建立文件索引
        $this->build_file_index();
        
        // 3. 觸發第一批掃描
        wp_schedule_single_event( time(), 'wpsa_scan_batch' );
    }
    
    /**
     * 掃描一個批次
     */
    public function scan_batch() {
        $start_time = microtime( true );
        $progress = $this->get_scan_progress();
        
        // 獲取待掃描文件
        $files = $this->get_pending_files( 
            $progress['offset'], 
            self::BATCH_SIZE 
        );
        
        foreach ( $files as $file ) {
            // 1. 時間保護 - 強制中斷機制
            if ( $this->should_stop_batch( $start_time ) ) {
                $this->save_progress_and_continue( $progress );
                return;
            }
            
            // 2. 記憶體保護
            if ( $this->is_memory_critical() ) {
                gc_collect_cycles();
                
                if ( $this->is_memory_critical() ) {
                    $this->save_progress_and_continue( $progress );
                    return;
                }
            }
            
            // 3. 執行三層檢測
            $this->scan_single_file( $file, $progress );
            
            // 4. 更新進度
            $progress['offset']++;
            $progress['scanned']++;
        }
        
        // 檢查是否完成
        if ( $progress['scanned'] >= $progress['total'] ) {
            $this->finalize_scan( $progress );
        } else {
            $this->save_progress_and_continue( $progress );
        }
    }
    
    /**
     * 判斷是否應該停止當前批次
     */
    private function should_stop_batch( $start_time ) {
        $elapsed = microtime( true ) - $start_time;
        return $elapsed > self::TIME_LIMIT;
    }
    
    /**
     * 檢查記憶體是否達到臨界值
     */
    private function is_memory_critical() {
        $limit = ini_get( 'memory_limit' );
        $limit_bytes = $this->convert_to_bytes( $limit );
        $current = memory_get_usage( true );
        
        $percentage = ( $current / $limit_bytes ) * 100;
        
        return $percentage > self::MEMORY_THRESHOLD;
    }
}

Action Scheduler 整合:

php
// 註冊 hook
add_action( 'wpsa_scan_batch', array( $scanner, 'scan_batch' ) );

// 排程下一批次 (在當前批次結束時)
function save_progress_and_continue( $progress ) {
    update_option( 'wpsa_scan_progress', $progress );
    
    // 立即排程下一批 (非同步執行)
    wp_schedule_single_event( time(), 'wpsa_scan_batch' );
}

前端進度顯示:

javascript
// AJAX 輪詢進度
function checkScanProgress() {
    jQuery.ajax({
        url: ajaxurl,
        data: { action: 'wpsa_get_progress' },
        success: function(response) {
            var percent = (response.scanned / response.total) * 100;
            jQuery('#progress-bar').css('width', percent + '%');
            jQuery('#progress-text').text(response.scanned + ' / ' + response.total);
            
            if (response.status === 'running') {
                setTimeout(checkScanProgress, 1000);
            } else if (response.status === 'completed') {
                showResults(response.threats);
            }
        }
    });
}

2. 三層檢測協定 (The Detection Protocol)

在掃描每一個檔案時,依照運算成本由低到高執行檢查。早期檢測到威脅或確認安全後,立即跳出,避免不必要的運算。

Layer 1: 快速篩選 (Fast Pass)

目標: 在 0.001 秒內判斷文件是否為已知安全或高度可疑

1.1 Checksum 白名單驗證

原理: WordPress 官方為每個版本的核心文件提供 MD5 checksums。如果文件的 hash 值與官方一致,可以直接跳過。

實現:

php
class WPSA_Checksum_Validator {
    
    private $core_checksums = null;
    
    /**
     * 驗證核心文件完整性
     */
    public function verify_core_file( $file_path ) {
        // 1. 確定 WordPress 版本
        global $wp_version;
        
        // 2. 獲取官方 checksums (帶緩存)
        if ( $this->core_checksums === null ) {
            $this->core_checksums = $this->get_core_checksums( $wp_version );
        }
        
        // 3. 計算相對路徑
        $relative_path = $this->get_relative_path( $file_path );
        
        // 4. 檢查是否為核心文件
        if ( ! isset( $this->core_checksums[ $relative_path ] ) ) {
            return null; // 不是核心文件,無法驗證
        }
        
        // 5. 比對 MD5
        $expected_md5 = $this->core_checksums[ $relative_path ];
        $actual_md5 = md5_file( $file_path );
        
        if ( $actual_md5 === $expected_md5 ) {
            return 'VERIFIED_SAFE'; // 通過驗證,安全
        } else {
            return 'MODIFIED_CORE'; // 核心文件被修改!
        }
    }
    
    /**
     * 從 WordPress.org API 獲取 checksums
     */
    private function get_core_checksums( $version ) {
        $transient_key = 'wpsa_checksums_' . $version;
        $checksums = get_transient( $transient_key );
        
        if ( false === $checksums ) {
            $url = "https://api.wordpress.org/core/checksums/1.0/?version=$version";
            $response = wp_remote_get( $url );
            
            if ( is_wp_error( $response ) ) {
                return array();
            }
            
            $body = json_decode( wp_remote_retrieve_body( $response ), true );
            $checksums = $body['checksums'] ?? array();
            
            // 緩存 24 小時
            set_transient( $transient_key, $checksums, DAY_IN_SECONDS );
        }
        
        return $checksums;
    }
    
    /**
     * 獲取相對於 ABSPATH 的路徑
     */
    private function get_relative_path( $file_path ) {
        $relative = str_replace( ABSPATH, '', $file_path );
        return str_replace( '\\', '/', $relative ); // Windows 路徑
    }
}

適用範圍:

  • ✅ WordPress 核心文件 (wp-admin/, wp-includes/, 根目錄的 .php)
  • ❌ 插件和主題 (需要額外的 API 或本地數據庫)
  • ❌ 第三方購買的高級插件/主題

檢測結果:

  • VERIFIED_SAFE: 文件完全一致,直接跳過後續檢測
  • MODIFIED_CORE: Critical 警報,核心文件被修改
  • null: 非核心文件,繼續後續檢測

1.2 Shannon Entropy (熵值) 計算

原理: Shannon Entropy 測量字串的隨機性/混亂度。惡意代碼通常會使用 Base64 或其他編碼來混淆,導致熵值異常高。

數學公式:

code
H(X) = -Σ P(xi) * log2(P(xi))

其中:
- P(xi) = 字符 xi 出現的概率
- H(X) = 熵值 (0-8 之間,8 為最高)

實現:

php
class WPSA_Entropy_Analyzer {
    
    const THRESHOLD = 5.5; // 閾值
    
    /**
     * 計算 Shannon Entropy
     */
    public function calculate_entropy( $string ) {
        $entropy = 0;
        $size = strlen( $string );
        
        if ( $size === 0 ) {
            return 0;
        }
        
        // 統計每個字符出現的次數
        $frequency = array_count_values( str_split( $string ) );
        
        foreach ( $frequency as $char => $count ) {
            $probability = $count / $size;
            $entropy -= $probability * log( $probability, 2 );
        }
        
        return $entropy;
    }
    
    /**
     * 檢查文件的熵值
     */
    public function check_file_entropy( $file_path ) {
        // 讀取文件內容
        $content = @file_get_contents( $file_path );
        
        if ( $content === false ) {
            return null; // 無法讀取
        }
        
        // 計算熵值
        $entropy = $this->calculate_entropy( $content );
        
        // 檢查是否超過閾值
        if ( $entropy > self::THRESHOLD ) {
            // 排除誤報
            if ( $this->is_false_positive( $file_path, $content ) ) {
                return 'SAFE';
            }
            
            return array(
                'threat' => 'HIGH_ENTROPY',
                'entropy' => $entropy,
                'severity' => 'MEDIUM',
            );
        }
        
        return 'SAFE';
    }
    
    /**
     * 排除已知的高熵但合法的文件
     */
    private function is_false_positive( $file_path, $content ) {
        // 1. 文件類型白名單
        $safe_extensions = array( 'jpg', 'jpeg', 'png', 'gif', 'ttf', 'woff', 'woff2' );
        $extension = pathinfo( $file_path, PATHINFO_EXTENSION );
        
        if ( in_array( strtolower( $extension ), $safe_extensions ) ) {
            return true;
        }
        
        // 2. Minified JS/CSS
        if ( preg_match('/\.(min\.js|min\.css)$/i', $file_path ) ) {
            return true;
        }
        
        // 3. node_modules 或 vendor 目錄
        if ( strpos( $file_path, 'node_modules' ) !== false || 
             strpos( $file_path, 'vendor' ) !== false ) {
            return true;
        }
        
        // 4. Data URI (Base64 圖片內嵌)
        if ( preg_match('/data:image\/[^;]+;base64,/i', $content ) ) {
            return true;
        }
        
        return false;
    }
}

熵值參考標準:

類型熵值範圍範例
正常 PHP 代碼3.5 - 4.5<?php echo "Hello"; ?>
英文文本4.0 - 4.5普通文章
Base64 編碼6.0 - 6.5YWJjZGVmZ2hpamtsbW5vcA==
加密/混淆代碼7.0 - 8.0\x4a\x8b\x3f\x9e...
閾值設定5.5平衡檢出率與誤報率

檢測結果:

  • SAFE: 熵值正常或為誤報
  • HIGH_ENTROPY: 高熵值,標記為可疑

Layer 2: 特徵碼比對 (Signature Matching)

目標: 精確匹配已知的惡意代碼模式

2.1 危險函數組合檢測

原理: 單一函數 (如 eval) 不一定是惡意的,但當與 base64_decode 組合使用時,幾乎肯定是後門。

高風險組合模式:

php
class WPSA_Signature_Scanner {
    
    /**
     * 危險函數組合特徵庫
     */
    private function get_malware_signatures() {
        return array(
            // 1. Eval + Base64 (最經典的後門)
            array(
                'name' => 'eval_base64',
                'pattern' => '/eval\s*\(\s*base64_decode/i',
                'severity' => 'CRITICAL',
                'description' => 'Eval with Base64 decode - Classic backdoor pattern',
                'example' => 'eval(base64_decode("ZXZhbCgkX1BPU1RbJ2NtZCddKTs="));',
            ),
            
            // 2. Assert + Base64 (PHP 7 之後常用的替代)
            array(
                'name' => 'assert_base64',
                'pattern' => '/assert\s*\(\s*base64_decode/i',
                'severity' => 'CRITICAL',
                'description' => 'Assert with Base64 - Alternative to eval()',
            ),
            
            // 3. Create Function (動態函數創建)
            array(
                'name' => 'create_function_base64',
                'pattern' => '/create_function\s*\(.*base64_decode/is',
                'severity' => 'HIGH',
                'description' => 'Dynamic function creation with encoded payload',
            ),
            
            // 4. Preg Replace /e modifier (PHP < 7.0 的 RCE)
            array(
                'name' => 'preg_replace_e',
                'pattern' => '/preg_replace\s*\(\s*[\'"][^\'\"]*\/e[\'\"]/i',
                'severity' => 'HIGH',
                'description' => 'Preg_replace with /e modifier - Code execution',
                'note' => 'Deprecated in PHP 5.5, removed in PHP 7.0',
            ),
            
            // 5. 系統命令執行 + 用戶輸入
            array(
                'name' => 'system_user_input',
                'pattern' => '/(system|exec|shell_exec|passthru|popen|proc_open)\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)/i',
                'severity' => 'CRITICAL',
                'description' => 'Direct OS command execution from user input',
            ),
            
            // 6. File Get Contents + External URL (C&C 連接)
            array(
                'name' => 'remote_file_inclusion',
                'pattern' => '/file_get_contents\s*\(\s*[\'"]https?:\/\//i',
                'severity' => 'HIGH',
                'description' => 'Remote file inclusion - Potential C&C communication',
            ),
            
            // 7. Gzinflate + Base64 (壓縮混淆)
            array(
                'name' => 'gzinflate_base64',
                'pattern' => '/gzinflate\s*\(\s*base64_decode/i',
                'severity' => 'HIGH',
                'description' => 'Gzinflate with Base64 - Compressed obfuscation',
            ),
            
            // 8. Str_rot13 + Base64 (雙重編碼)
            array(
                'name' => 'str_rot13_base64',
                'pattern' => '/str_rot13\s*\(\s*base64_decode/i',
                'severity' => 'HIGH',
                'description' => 'ROT13 with Base64 - Double encoding obfuscation',
            ),
            
            // 9. PHP Webshell 常見指紋
            array(
                'name' => 'webshell_fingerprint',
                'pattern' => '/(\$_FILES.*move_uploaded_file|\$_GET.*passthru|c99shell|r57shell|wso\.php)/i',
                'severity' => 'CRITICAL',
                'description' => 'Known webshell fingerprint detected',
            ),
        );
    }
    
    /**
     * 掃描文件中的惡意特徵
     */
    public function scan_file_signatures( $content ) {
        $threats = array();
        $signatures = $this->get_malware_signatures();
        
        foreach ( $signatures as $sig ) {
            if ( preg_match( $sig['pattern'], $content, $matches ) ) {
                $threats[] = array(
                    'type' => 'MALWARE_SIGNATURE',
                    'signature_name' => $sig['name'],
                    'severity' => $sig['severity'],
                    'description' => $sig['description'],
                    'matched_code' => $this->extract_context( $content, $matches[0] ),
                );
            }
        }
        
        return $threats;
    }
    
    /**
     * 提取匹配代碼的上下文 (前後各 2 行)
     */
    private function extract_context( $content, $matched_string ) {
        $lines = explode( "\n", $content );
        $match_line = 0;
        
        // 找到匹配的行號
        foreach ( $lines as $index => $line ) {
            if ( strpos( $line, $matched_string ) !== false ) {
                $match_line = $index;
                break;
            }
        }
        
        // 提取上下文 (前後各 2 行)
        $start = max( 0, $match_line - 2 );
        $end = min( count( $lines ) - 1, $match_line + 2 );
        
        $context_lines = array_slice( $lines, $start, $end - $start + 1 );
        
        return array(
            'line_number' => $match_line + 1,
            'code' => implode( "\n", $context_lines ),
        );
    }
}

2.2 混淆技術偵測

常見混淆手法:

php
class WPSA_Obfuscation_Detector {
    
    /**
     * 檢測混淆技術
     */
    public function detect_obfuscation( $content ) {
        $indicators = array();
        
        // 1. 字串反轉 (strrev)
        if ( preg_match('/strrev\s*\(\s*[\'"][^\'"]+[\'"]\s*\)/i', $content, $matches ) ) {
            $reversed_string = $matches[0];
            
            // 檢查反轉後是否為危險函數名
            if ( preg_match('/strrev\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)/i', $reversed_string, $inner ) ) {
                $original = strrev( $inner[1] );
                $dangerous = array( 'eval', 'assert', 'system', 'exec', 'base64_decode' );
                
                if ( in_array( $original, $dangerous ) ) {
                    $indicators['string_reversal'] = array(
                        'severity' => 'HIGH',
                        'description' => "Reversed function name: $original",
                    );
                }
            }
        }
        
        // 2. 過度字串拼接 (超過 10 次)
        $concat_count = preg_match_all('/\'\s*\.\s*\'/i', $content );
        if ( $concat_count > 10 ) {
            $indicators['excessive_concatenation'] = array(
                'severity' => 'MEDIUM',
                'description' => "Excessive string concatenation: $concat_count times",
                'note' => 'Often used to obfuscate function names',
            );
        }
        
        // 3. 變變數 (Variable Variables: $$var)
        if ( preg_match('/\$\$\w+/i', $content ) ) {
            $indicators['variable_variables'] = array(
                'severity' => 'MEDIUM',
                'description' => 'Use of variable variables ($$var)',
                'note' => 'Can be used to hide function calls',
            );
        }
        
        // 4. Hex 編碼字串 (超過 5 個)
        $hex_count = preg_match_all('/\\\\x[0-9a-f]{2}/i', $content );
        if ( $hex_count > 5 ) {
            $indicators['hex_encoding'] = array(
                'severity' => 'HIGH',
                'description' => "Hex-encoded strings detected: $hex_count",
                'example' => '\x65\x76\x61\x6c = eval',
            );
        }
        
        // 5. Chr/Ord 濫用 (超過 5 次)
        $chr_count = preg_match_all('/chr\s*\(\s*\d+\s*\)/i', $content );
        if ( $chr_count > 5 ) {
            $indicators['chr_abuse'] = array(
                'severity' => 'MEDIUM',
                'description' => "Excessive use of chr(): $chr_count times",
                'note' => 'chr(101).chr(118).chr(97).chr(108) = eval',
            );
        }
        
        // 6. GLOBALS 濫用 (超過 3 次)
        $globals_count = preg_match_all('/\$GLOBALS\[/i', $content );
        if ( $globals_count > 3 ) {
            $indicators['globals_abuse'] = array(
                'severity' => 'MEDIUM',
                'description' => "Excessive use of \$GLOBALS: $globals_count times",
            );
        }
        
        // 7. 註釋混淆 (在函數名中插入註釋)
        if ( preg_match('/(eval|system|assert)\/\*.*?\*\//i', $content ) ) {
            $indicators['comment_obfuscation'] = array(
                'severity' => 'HIGH',
                'description' => 'Function names split by comments',
                'example' => 'ev/*comment*/al($code)',
            );
        }
        
        return $indicators;
    }
}

2.3 WordPress 特定漏洞指紋

php
/**
 * 檢測已知的 WordPress 插件/主題漏洞
 */
private function get_wordpress_vulnerability_signatures() {
    return array(
        // TimThumb 遠程文件包含
        array(
            'name' => 'timthumb_rfi',
            'file_pattern' => 'timthumb\.php$',
            'content_pattern' => '/webshot\.php|src=.*?https?:\/\//i',
            'cve' => 'CVE-2011-4106',
            'severity' => 'CRITICAL',
            'description' => 'TimThumb Remote File Inclusion vulnerability',
        ),
        
        // Revolution Slider 任意文件下載
        array(
            'name' => 'revslider_file_download',
            'file_pattern' => 'revslider',
            'content_pattern' => '/force-download|download-file/i',
            'cve' => 'CVE-2015-1579',
            'severity' => 'HIGH',
            'description' => 'Revolution Slider arbitrary file download',
        ),
        
        // MailPoet 未授權上傳
        array(
            'name' => 'mailpoet_upload',
            'file_pattern' => 'mailpoet',
            'content_pattern' => '/wysija_upload/i',
            'cve' => 'CVE-2014-8326',
            'severity' => 'CRITICAL',
            'description' => 'MailPoet unauthorized file upload',
        ),
        
        // WP-VCD 惡意軟體 (functions.php 注入)
        array(
            'name' => 'wp_vcd_malware',
            'file_pattern' => 'functions\.php$',
            'content_pattern' => '/wp-vcd\.php|wp-tmp\.php/i',
            'severity' => 'CRITICAL',
            'description' => 'WP-VCD malware infection in theme',
        ),
    );
}

Layer 3: 啟發式分析 (Heuristic Analysis)

目標: 檢測未知的威脅,基於行為和異常模式

3.1 異常位置檢測

php
class WPSA_Location_Analyzer {
    
    /**
     * 檢查文件位置是否可疑
     */
    public function check_suspicious_location( $file_path ) {
        $threats = array();
        
        // 規則 1: Uploads 目錄不應有 PHP 文件
        if ( $this->is_in_uploads( $file_path ) && $this->is_php_file( $file_path ) ) {
            $threats[] = array(
                'type' => 'PHP_IN_UPLOADS',
                'severity' => 'HIGH',
                'description' => 'PHP file found in uploads directory',
                'reason' => 'Uploads should only contain media files',
                'recommendation' => 'Delete this file unless you explicitly uploaded it',
            );
        }
        
        // 規則 2: 可疑的文件名模式
        $suspicious_filenames = array(
            '/^(config|setup|install|mysql|db|backup|dump|shell|cmd|admin|login|test)\.php$/i',
            '/^(c99|r57|wso|b374k|idx|sym|bypass)\.php$/i', // 已知 webshell 名稱
            '/^[a-f0-9]{32}\.php$/i', // MD5 雜湊命名
            '/^\d{10,}\.php$/i', // Unix timestamp 命名
        );
        
        $filename = basename( $file_path );
        
        foreach ( $suspicious_filenames as $pattern ) {
            if ( preg_match( $pattern, $filename ) ) {
                $threats[] = array(
                    'type' => 'SUSPICIOUS_FILENAME',
                    'severity' => 'MEDIUM',
                    'description' => "Suspicious filename pattern: $filename",
                );
            }
        }
        
        // 規則 3: 隱藏文件 (以 . 開頭)
        if ( preg_match('/^\.[\w-]+\.php$/i', $filename ) ) {
            $threats[] = array(
                'type' => 'HIDDEN_PHP_FILE',
                'severity' => 'MEDIUM',
                'description' => 'Hidden PHP file (starts with dot)',
            );
        }
        
        // 規則 4: 核心目錄中的非核心文件
        if ( $this->is_in_core_directory( $file_path ) && ! $this->is_core_file( $file_path ) ) {
            $threats[] = array(
                'type' => 'UNKNOWN_FILE_IN_CORE',
                'severity' => 'HIGH',
                'description' => 'Unknown file in WordPress core directory',
            );
        }
        
        return $threats;
    }
    
    private function is_in_uploads( $file_path ) {
        $upload_dir = wp_upload_dir();
        return strpos( $file_path, $upload_dir['basedir'] ) === 0;
    }
    
    private function is_in_core_directory( $file_path ) {
        $core_dirs = array( 'wp-admin', 'wp-includes' );
        
        foreach ( $core_dirs as $dir ) {
            if ( strpos( $file_path, ABSPATH . $dir ) === 0 ) {
                return true;
            }
        }
        
        return false;
    }
}

3.2 時間戳記異常檢測

php
class WPSA_Timestamp_Analyzer {
    
    /**
     * 檢測文件時間戳記異常
     */
    public function detect_timestamp_anomaly( $file_path ) {
        $modified_time = filemtime( $file_path );
        $current_time = time();
        $anomalies = array();
        
        // 異常 1: 核心文件在最近 24 小時內被修改
        if ( $this->is_core_file( $file_path ) ) {
            $age_hours = ( $current_time - $modified_time ) / HOUR_IN_SECONDS;
            
            if ( $age_hours < 24 ) {
                $anomalies[] = array(
                    'type' => 'RECENTLY_MODIFIED_CORE',
                    'severity' => 'HIGH',
                    'description' => sprintf(
                        'Core file modified %d hours ago',
                        round( $age_hours )
                    ),
                    'modified_time' => date( 'Y-m-d H:i:s', $modified_time ),
                );
            }
        }
        
        // 異常 2: 未來時間戳記 (伺服器時間被竄改?)
        if ( $modified_time > $current_time + HOUR_IN_SECONDS ) {
            $anomalies[] = array(
                'type' => 'FUTURE_TIMESTAMP',
                'severity' => 'MEDIUM',
                'description' => 'File has future modification time',
                'modified_time' => date( 'Y-m-d H:i:s', $modified_time ),
            );
        }
        
        // 異常 3: 同目錄下唯一的新文件 (孤立注入)
        $dir_files = glob( dirname( $file_path ) . '/*.php' );
        $recent_threshold = $current_time - ( 7 * DAY_IN_SECONDS ); // 一週內
        
        $recent_files = array_filter( $dir_files, function( $f ) use ( $recent_threshold ) {
            return filemtime( $f ) > $recent_threshold;
        });
        
        if ( count( $recent_files ) === 1 && in_array( $file_path, $recent_files ) ) {
            $anomalies[] = array(
                'type' => 'LONE_RECENT_FILE',
                'severity' => 'MEDIUM',
                'description' => 'Only recently modified file in directory',
                'note' => 'Could indicate isolated injection',
            );
        }
        
        return $anomalies;
    }
}

3.3 代碼行為分析

php
class WPSA_Behavior_Analyzer {
    
    /**
     * 分析代碼的可疑行為
     */
    public function analyze_code_behavior( $content, $file_path ) {
        $behaviors = array();
        
        // 行為 1: 網路通訊能力 (C&C 連接)
        if ( $this->has_network_capability( $content ) ) {
            $behaviors[] = array(
                'type' => 'NETWORK_COMMUNICATION',
                'severity' => 'MEDIUM',
                'description' => 'Code makes external HTTP requests',
                'indicators' => $this->extract_urls( $content ),
            );
        }
        
        // 行為 2: 文件寫入能力 (持久化)
        if ( $this->has_file_write_capability( $content ) ) {
            $behaviors[] = array(
                'type' => 'FILE_WRITING',
                'severity' => 'LOW',
                'description' => 'Code can write to files',
            );
        }
        
        // 行為 3: 繞過 wpdb 直接操作資料庫
        if ( $this->has_direct_db_access( $content ) ) {
            $behaviors[] = array(
                'type' => 'DIRECT_DB_ACCESS',
                'severity' => 'MEDIUM',
                'description' => 'Direct MySQL access (bypassing $wpdb)',
                'note' => 'May indicate database backdoor',
            );
        }
        
        // 行為 4: Email 發送 + 用戶輸入 (spam backdoor)
        if ( $this->can_send_email_with_user_input( $content ) ) {
            $behaviors[] = array(
                'type' => 'EMAIL_SENDING',
                'severity' => 'HIGH',
                'description' => 'Can send emails with user-controlled content',
                'note' => 'Common spam backdoor technique',
            );
        }
        
        // 行為 5: 過度使用錯誤抑制 (@)
        $suppression_count = preg_match_all('/@(file_|fopen|include|require|eval|system)/i', $content );
        
        if ( $suppression_count > 3 ) {
            $behaviors[] = array(
                'type' => 'ERROR_SUPPRESSION',
                'severity' => 'MEDIUM',
                'description' => "Excessive error suppression: $suppression_count instances",
                'note' => 'Used to hide malicious activity from logs',
            );
        }
        
        return $behaviors;
    }
    
    private function has_network_capability( $content ) {
        return preg_match('/(file_get_contents|curl_exec|fsockopen|stream_socket_client)\s*\(\s*[\'"]https?:\/\//i', $content );
    }
    
    private function extract_urls( $content ) {
        preg_match_all('/https?:\/\/[^\s\'"]+/i', $content, $matches );
        return array_unique( $matches[0] );
    }
    
    private function can_send_email_with_user_input( $content ) {
        $has_mail = preg_match('/(mail|wp_mail)\s*\(/i', $content );
        $has_input = preg_match('/\$_(GET|POST|REQUEST|COOKIE)/i', $content );
        
        return $has_mail && $has_input;
    }
}

3. 檔案操作安全 (File Safety)

隔離機制 (Quarantine System)

絕對禁止直接刪除: 刪除文件可能導致網站崩潰或誤刪正常文件。

php
class WPSA_Quarantine_Manager {
    
    const QUARANTINE_DIR = WP_CONTENT_DIR . '/uploads/wpsa-quarantine/';
    
    /**
     * 初始化隔離區
     */
    public function init_quarantine() {
        // 創建隔離目錄
        if ( ! file_exists( self::QUARANTINE_DIR ) ) {
            wp_mkdir_p( self::QUARANTINE_DIR );
        }
        
        // 創建 .htaccess 阻止執行
        $htaccess_content = "# WPSA Quarantine - Deny all access\n";
        $htaccess_content .= "Order deny,allow\n";
        $htaccess_content .= "Deny from all\n";
        
        file_put_contents( 
            self::QUARANTINE_DIR . '.htaccess', 
            $htaccess_content 
        );
        
        // 創建 index.php 防止目錄瀏覽
        file_put_contents( 
            self::QUARANTINE_DIR . 'index.php', 
            '<?php // Silence is golden' 
        );
    }
    
    /**
     * 隔離可疑文件
     */
    public function quarantine_file( $file_path, $threat_info ) {
        // 生成唯一的隔離文件名
        $timestamp = date( 'Y-m-d_His' );
        $hash = substr( md5( $file_path ), 0, 8 );
        $original_name = basename( $file_path );
        
        $quarantine_name = sprintf(
            '%s_%s_%s.suspected',
            $timestamp,
            $hash,
            $original_name
        );
        
        $quarantine_path = self::QUARANTINE_DIR . $quarantine_name;
        
        // 移動文件到隔離區
        $moved = rename( $file_path, $quarantine_path );
        
        if ( ! $moved ) {
            // 如果無法移動,嘗試複製然後刪除
            if ( copy( $file_path, $quarantine_path ) ) {
                unlink( $file_path );
                $moved = true;
            }
        }
        
        if ( $moved ) {
            // 記錄隔離信息
            $this->log_quarantine( $file_path, $quarantine_path, $threat_info );
            
            return array(
                'success' => true,
                'quarantine_path' => $quarantine_path,
                'message' => 'File successfully quarantined',
            );
        }
        
        return array(
            'success' => false,
            'error' => 'Failed to quarantine file',
        );
    }
    
    /**
     * 記錄隔離操作
     */
    private function log_quarantine( $original_path, $quarantine_path, $threat_info ) {
        $log_entry = array(
            'timestamp' => current_time( 'mysql' ),
            'original_path' => $original_path,
            'quarantine_path' => $quarantine_path,
            'threat_type' => $threat_info['type'],
            'severity' => $threat_info['severity'],
            'description' => $threat_info['description'],
        );
        
        $quarantine_log = get_option( 'wpsa_quarantine_log', array() );
        $quarantine_log[] = $log_entry;
        
        update_option( 'wpsa_quarantine_log', $quarantine_log );
    }
    
    /**
     * 恢復隔離文件 (管理員手動操作)
     */
    public function restore_file( $quarantine_path, $original_path ) {
        if ( ! current_user_can( 'manage_options' ) ) {
            return array( 'success' => false, 'error' => 'Unauthorized' );
        }
        
        // 確認文件存在
        if ( ! file_exists( $quarantine_path ) ) {
            return array( 'success' => false, 'error' => 'File not found' );
        }
        
        // 恢復文件
        $restored = rename( $quarantine_path, $original_path );
        
        if ( $restored ) {
            // 更新日誌
            $this->log_restore( $quarantine_path, $original_path );
            
            return array(
                'success' => true,
                'message' => 'File restored successfully',
            );
        }
        
        return array( 'success' => false, 'error' => 'Failed to restore file' );
    }
}

Complete Scanning Workflow (完整掃描工作流程)

完整的掃描生命周期

php
class WPSA_Scanner_Orchestrator {
    
    /**
     * Phase 1: 初始化掃描
     */
    public function start_scan( $scan_options = array() ) {
        // 1. 設置 PHP 環境
        $this->configure_php_environment();
        
        // 2. 創建掃描會話
        $scan_id = $this->create_scan_session();
        
        // 3. 建立文件索引
        $file_index = $this->build_file_index( $scan_options );
        
        // 4. 初始化進度追蹤
        $this->init_progress_tracking( $scan_id, count( $file_index ) );
        
        // 5. 觸發第一批掃描
        wp_schedule_single_event( time(), 'wpsa_scan_batch', array( $scan_id ) );
        
        return array(
            'scan_id' => $scan_id,
            'total_files' => count( $file_index ),
            'status' => 'initiated',
        );
    }
    
    /**
     * Phase 2: 分批掃描執行
     */
    public function execute_batch( $scan_id ) {
        $start_time = microtime( true );
        $progress = $this->get_progress( $scan_id );
        
        // 獲取待掃描文件
        $files = $this->get_next_batch( $progress );
        
        foreach ( $files as $file ) {
            // 時間檢查
            if ( $this->should_stop_batch( $start_time ) ) {
                $this->schedule_next_batch( $scan_id );
                return;
            }
            
            // 執行三層檢測
            $threats = $this->scan_file_complete( $file );
            
            // 如果發現威脅,執行處理
            if ( ! empty( $threats ) ) {
                $this->handle_threats( $file, $threats );
            }
            
            // 更新進度
            $this->update_progress( $scan_id, $file );
            
            // 記憶體管理
            if ( $progress['scanned'] % 10 === 0 ) {
                gc_collect_cycles();
            }
        }
        
        // 檢查是否完成
        if ( $this->is_scan_complete( $scan_id ) ) {
            $this->finalize_scan( $scan_id );
        } else {
            $this->schedule_next_batch( $scan_id );
        }
    }
    
    /**
     * 執行單個文件的完整三層檢測
     */
    private function scan_file_complete( $file_path ) {
        $all_threats = array();
        
        // Layer 1: 快速篩選
        $checksum_result = $this->verify_checksum( $file_path );
        
        if ( $checksum_result === 'VERIFIED_SAFE' ) {
            return array(); // 文件安全,跳過
        }
        
        if ( $checksum_result === 'MODIFIED_CORE' ) {
            $all_threats[] = array(
                'layer' => 1,
                'type' => 'MODIFIED_CORE_FILE',
                'severity' => 'CRITICAL',
            );
        }
        
        // 讀取文件內容 (用於後續檢測)
        $content = $this->safe_file_read( $file_path );
        
        if ( $content === false ) {
            return array(); // 無法讀取,跳過
        }
        
        // Layer 1: 熵值檢測
        $entropy_result = $this->check_entropy( $content );
        
        if ( $entropy_result !== 'SAFE' ) {
            $all_threats[] = $entropy_result;
        }
        
        // Layer 2: 特徵碼匹配
        $signature_threats = $this->scan_signatures( $content );
        $all_threats = array_merge( $all_threats, $signature_threats );
        
        // Layer 2: 混淆檢測
        $obfuscation_threats = $this->detect_obfuscation( $content );
        $all_threats = array_merge( $all_threats, $obfuscation_threats );
        
        // Layer 3: 位置檢測
        $location_threats = $this->check_location( $file_path );
        $all_threats = array_merge( $all_threats, $location_threats );
        
        // Layer 3: 時間戳記檢測
        $timestamp_threats = $this->check_timestamp( $file_path );
        $all_threats = array_merge( $all_threats, $timestamp_threats );
        
        // Layer 3: 行為分析
        $behavior_threats = $this->analyze_behavior( $content, $file_path );
        $all_threats = array_merge( $all_threats, $behavior_threats );
        
        return $all_threats;
    }
    
    /**
     * Phase 3: 生成掃描報告
     */
    public function generate_report( $scan_id ) {
        $progress = $this->get_progress( $scan_id );
        $threats = $this->get_all_threats( $scan_id );
        
        // 威脅分類
        $categorized = $this->categorize_threats( $threats );
        
        $report = array(
            'scan_id' => $scan_id,
            'timestamp' => current_time( 'mysql' ),
            'summary' => array(
                'total_files' => $progress['total'],
                'scanned_files' => $progress['scanned'],
                'threats_found' => count( $threats ),
                'critical' => $categorized['CRITICAL'],
                'high' => $categorized['HIGH'],
                'medium' => $categorized['MEDIUM'],
                'low' => $categorized['LOW'],
                'scan_duration' => $progress['end_time'] - $progress['start_time'],
            ),
            'threats' => $threats,
            'recommendations' => $this->generate_recommendations( $threats ),
        );
        
        return $report;
    }
}

Error Handling & Fault Tolerance (錯誤處理)

php
class WPSA_Error_Handler {
    
    /**
     * 安全的文件讀取
     */
    public function safe_file_read( $file_path ) {
        try {
            // 檢查 1: 文件是否存在
            if ( ! file_exists( $file_path ) ) {
                $this->log_error( 'FILE_NOT_FOUND', $file_path );
                return false;
            }
            
            // 檢查 2: 是否可讀
            if ( ! is_readable( $file_path ) ) {
                $this->log_error( 'FILE_NOT_READABLE', $file_path );
                return false;
            }
            
            // 檢查 3: 文件大小限制
            $max_size = 5 * MB_IN_BYTES; // 5MB
            $file_size = filesize( $file_path );
            
            if ( $file_size > $max_size ) {
                $this->log_warning( 'FILE_TOO_LARGE', $file_path, $file_size );
                
                // 分塊讀取大文件
                return $this->read_file_in_chunks( $file_path );
            }
            
            // 讀取文件
            $content = @file_get_contents( $file_path );
            
            if ( $content === false ) {
                $this->log_error( 'FILE_READ_FAILED', $file_path );
                return false;
            }
            
            return $content;
            
        } catch ( Exception $e ) {
            $this->log_exception( 'FILE_READ_EXCEPTION', $file_path, $e );
            return false;
        }
    }
    
    /**
     * 分塊讀取大文件
     */
    private function read_file_in_chunks( $file_path ) {
        $handle = @fopen( $file_path, 'r' );
        
        if ( ! $handle ) {
            return false;
        }
        
        $content = '';
        $chunk_size = 1024 * 1024; // 1MB chunks
        
        while ( ! feof( $handle ) ) {
            $content .= fread( $handle, $chunk_size );
        }
        
        fclose( $handle );
        
        return $content;
    }
    
    /**
     * 記錄錯誤
     */
    private function log_error( $error_type, $file_path, $extra = null ) {
        $log_entry = array(
            'timestamp' => current_time( 'mysql' ),
            'type' => 'ERROR',
            'code' => $error_type,
            'file' => $file_path,
            'extra' => $extra,
        );
        
        $error_log = get_option( 'wpsa_error_log', array() );
        $error_log[] = $log_entry;
        
        // 只保留最近 100 條錯誤
        if ( count( $error_log ) > 100 ) {
            $error_log = array_slice( $error_log, -100 );
        }
        
        update_option( 'wpsa_error_log', $error_log );
    }
}

Response Format (報告格式)

掃描報告結構

markdown
# 🔒 WordPress 惡意軟體掃描報告

## 📊 掃描總結
- **掃描 ID**: {scan_id}
- **掃描時間**: {timestamp}
- **總文件數**: {total_files}
- **已掃描**: {scanned_files}
- **發現威脅**: {threats_found}
- **掃描耗時**: {duration} 秒

## 🚨 威脅等級分布
- 🔴 **Critical**: {critical_count} 個
- 🟠 **High**: {high_count} 個
- 🟡 **Medium**: {medium_count} 個
- 🟢 **Low**: {low_count} 個

## ⚠️ 檢測到的威脅

### [#1] Eval + Base64 Backdoor - Critical 🔴

**文件位置**: `wp-content/themes/mytheme/footer.php`  
**威脅類型**: MALWARE_SIGNATURE (eval_base64)  
**嚴重性**: Critical  
**檢測層級**: Layer 2 (Signature Matching)

**問題代碼** (Line 45):
```php
<?php
// ... 正常代碼 ...

eval(base64_decode('ZXZhbCgkX1BPU1RbJ2NtZCddKTs=')); // ← 惡意代碼

// ... 正常代碼 ...
?>

Base64 解碼後:

php
eval($_POST['cmd']);

威脅說明: 這是一個典型的 PHP 後門,允許攻擊者通過 POST 請求執行任意 PHP 代碼。攻擊者可以:

  • 讀取/修改/刪除任何文件
  • 訪問數據庫
  • 上傳更多惡意代碼
  • 完全控制網站

建議操作:

  1. 立即隔離: 文件已自動移動到隔離區
  2. 🔍 檢查影響: 查看此文件的最後修改時間,確認被入侵的時間範圍
  3. 🔑 更改密碼: 重置所有管理員密碼和 FTP/SSH 憑證
  4. 🛡️ 修補漏洞: 確認攻擊者如何上傳此文件,修補相關漏洞

[#2] PHP in Uploads Directory - High 🟠

文件位置: wp-content/uploads/2024/03/shell.php
威脅類型: PHP_IN_UPLOADS
嚴重性: High
檢測層級: Layer 3 (Location Analysis)

威脅說明: Uploads 目錄通常只應包含媒體文件 (圖片、PDF 等),不應有可執行的 PHP 文件。

建議操作:

  1. 已隔離
  2. 🔍 檢查上傳漏洞: 確認是否有插件允許未經驗證的文件上傳

📋 建議後續行動

🔴 緊急行動 (Critical)

  1. 立即隔離所有 Critical 級別的文件
  2. 更改所有密碼 (WordPress, FTP, SSH, 資料庫)
  3. 審查管理員帳戶,刪除可疑帳戶
  4. 檢查近期的文件修改記錄

🟠 重要行動 (High)

  1. 審查所有 High 級別威脅
  2. 更新所有插件和主題到最新版本
  3. 安裝 WordPress 防火牆 (如 Wordfence, Sucuri)

🟡 改進行動 (Medium/Low)

  1. 定期進行安全掃描
  2. 啟用雙因素認證
  3. 限制文件上傳類型
  4. 設置文件權限為 644 (文件) 和 755 (目錄)

🔧 系統健康檢查

  • ✅ WordPress 核心文件完整性: 98% 通過
  • ❌ 發現 2 個被修改的核心文件
  • ✅ 插件/主題: 15 個已掃描,2 個發現威脅
  • ⚠️ 文件權限: 3 個文件可被其他用戶寫入

📞 需要幫助?

如果您需要專業的網站清理服務,請聯繫:

  • WordPress 安全團隊
  • 專業的網站安全公司 (Sucuri, Wordfence, SiteLock)
code

---

# Best Practices & Recommendations

## 開發指南

1. **永遠使用分批處理**: 即使只有 100 個文件,也要分批
2. **時間限制**: 每批次不超過 3 秒
3. **記憶體監控**: 使用 `memory_get_usage()` 監控
4. **錯誤容忍**: 單個文件失敗不應影響整體掃描
5. **進度可視化**: 提供實時進度條給用戶
6. **可中斷恢復**: 用戶關閉頁面後能自動繼續

## 性能優化

```php
// 使用 OpCache (如果可用)
if ( function_exists( 'opcache_compile_file' ) ) {
    opcache_compile_file( $file_path );
}

// 定期清理內存
if ( $count % 10 === 0 ) {
    gc_collect_cycles();
}

// 使用生成器節省記憶體
function get_files_generator( $directory ) {
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator( $directory )
    );
    
    foreach ( $iterator as $file ) {
        yield $file;
    }
}

安全建議

  1. 隔離而非刪除: 給用戶恢復的機會
  2. 權限檢查: 只有管理員能執行掃描
  3. Nonce 驗證: 所有 AJAX 請求都要驗證
  4. 日誌記錄: 記錄所有重要操作
  5. 通知機制: 發現 Critical 威脅時發送郵件

Continuous Learning

作為惡意軟體獵人,應該持續學習:

資源推薦

實戰案例

定期研究真實的入侵案例:

  • 攻擊者如何進入系統?
  • 使用了什麼混淆技術?
  • 如何改進檢測規則?

記住核心原則:

  1. 永不炸站 (Don't Break the Site)
  2. 縱深防禦 (Defense in Depth)
  3. 故障安全 (Fail-Safe)