首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 檢測與清除網頁木馬掛馬代碼實戰

     編輯:PHP 相關知識

網頁惡意代碼注入檢測與自動清理方案

網站被植入惡意腳本(如隱藏 iframe、跳轉代碼、挖礦腳本)是常見安全事件。本文分析一個基於文件掃描的自動清理腳本,並提供更完善的檢測策略、防範措施及應急響應流程。

原始清理腳本分析

<?php
class ClearVirus {
    public $index = 'b.html';           // 目標文件
    public $filepath = ['b.html'];      // 掃描列表
    public $virus_type = "<script src=https://%61%76%65%33%2E%63%6E></script>";  // 特徵碼
    
    function open_file() {
        if (file_exists($this->index)) {
            $tmp = file_get_contents($this->index);
            
            // 檢測特徵碼
            if (strpos($tmp, $this->virus_type) !== false) {
                // 替換清除
                $clean = str_replace($this->virus_type, '', $tmp);
                $handle = fopen($this->index, 'w');
                fwrite($handle, $clean);
                fclose($handle);
                echo "已清除惡意代碼";
            } else {
                echo "未發現指定特徵碼";
            }
        }
    }
}

$virus = new ClearVirus;
$virus->open_file();
?>

⚠️ 原方案的嚴重缺陷

關鍵問題

  • 特徵碼過於具體:僅能清除一種特定編碼的惡意腳本,變體即可繞過
  • 單文件掃描:僅檢查 b.html,忽略其他入口文件
  • 無備份機制:直接覆蓋原文件,誤刪無法恢復
  • 被動觸發:需訪問頁面才執行,無法及時發現
  • 無日誌審計:無法追蹤清理歷史、攻擊來源
  • 編碼繞過:攻擊者可用 Unicode、Base64、十六進制等編碼隱藏代碼

增強版掃描清理類

<?php
class WebShellScanner {
    private $scanDir;
    private $backupDir;
    private $logFile;
    private $patterns = [];
    private $whitelist = [];
    private $results = [];
    
    public function __construct($scanDir, $backupDir = null, $logFile = 'scan.log') {
        $this->scanDir = rtrim($scanDir, '/');
        $this->backupDir = $backupDir ?: $this->scanDir . '/.backup_' . date('Ymd_His');
        $this->logFile = $logFile;
        $this->initPatterns();
    }
    
    // 初始化惡意代碼特徵庫
    private function initPatterns() {
        $this->patterns = [
            // 常見惡意模式(正則表達式)
            'eval_base64' => '/eval\s*\(\s*base64_decode\s*\(/i',
            'eval_gzinflate' => '/eval\s*\(\s*gzinflate\s*\(/i',
            'assert_attack' => '/assert\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)/i',
            'create_function' => '/create_function\s*\(/i',
            'preg_replace_e' => '/preg_replace\s*\(.*\/e/i',
            'shell_exec' => '/(shell_exec|exec|system|passthru|popen|proc_open)\s*\(/i',
            'file_put_get' => '/file_put_contents\s*\(.*\$_(GET|POST)/i',
            'iframe_inject' => '/<iframe.*src=.*><\/iframe>/i',
            'script_src_hex' => '/<script\s+src=.*(%[0-9A-F]{2}){4,}/i',
            'document_write' => '/document\.write\s*\(.*unescape/i',
            'eval_hex' => '/eval\s*\(\s*["\']?\s*\\\\x[0-9a-f]{2}/i',
            'str_rot13' => '/str_rot13\s*\(/i',
            'goto_obfuscation' => '/goto\s+[a-zA-Z_]+;/i',
        ];
        
        // 白名單:合法使用上述函數的文件
        $this->whitelist = [
            'vendor/', 'cache/', 'storage/logs/', '.git/'
        ];
    }
    
    // 掃描目錄
    public function scan() {
        $this->results = [
            'scanned' => 0,
            'infected' => 0,
            'cleaned' => 0,
            'errors' => 0,
            'details' => []
        ];
        
        $this->scanDirectory($this->scanDir);
        $this->writeLog();
        return $this->results;
    }
    
    private function scanDirectory($dir) {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        
        foreach ($iterator as $file) {
            if (!$file->isFile()) continue;
            $path = $file->getPathname();
            
            // 跳過白名單
            if ($this->isWhitelisted($path)) continue;
            
            // 僅掃描文本類文件
            $ext = strtolower($file->getExtension());
            if (!in_array($ext, ['php', 'html', 'htm', 'js', 'css', 'txt', 'inc', 'tpl', 'phtml'])) continue;
            
            $this->scanFile($path);
        }
    }
    
    private function scanFile($path) {
        $this->results['scanned']++;
        $relativePath = substr($path, strlen($this->scanDir) + 1);
        
        $content = @file_get_contents($path);
        if ($content === false) {
            $this->results['errors']++;
            $this->log("ERROR: 無法讀取 $relativePath");
            return;
        }
        
        $matches = [];
        foreach ($this->patterns as $name => $pattern) {
            if (preg_match($pattern, $content)) {
                $matches[] = $name;
            }
        }
        
        if ($matches) {
            $this->results['infected']++;
            $this->results['details'][] = [
                'file' => $relativePath,
                'patterns' => $matches,
                'size' => filesize($path),
                'mtime' => date('Y-m-d H:i:s', filemtime($path))
            ];
            $this->log("INFECTED: $relativePath [" . implode(', ', $matches) . "]");
        }
    }
    
    // 自動清理(需謹慎使用)
    public function clean($dryRun = true) {
        foreach ($this->results['details'] as $detail) {
            $path = $this->scanDir . '/' . $detail['file'];
            $content = file_get_contents($path);
            $original = $content;
            
            // 僅移除匹配的惡意片段(示例:移除 eval(base64_decode(...)) 結構)
            foreach ($detail['patterns'] as $patternName) {
                $pattern = $this->patterns[$patternName];
                // 這裡需極其謹慎,建議人工審核後手動清理
                // $content = preg_replace($pattern, '', $content);
            }
            
            if ($content !== $original) {
                if (!$dryRun) {
                    $this->backupFile($path);
                    file_put_contents($path, $content);
                    $this->results['cleaned']++;
                    $this->log("CLEANED: {$detail['file']}");
                } else {
                    $this->log("DRY-RUN: Would clean {$detail['file']}");
                }
            }
        }
    }
    
    private function backupFile($path) {
        if (!is_dir($this->backupDir)) mkdir($this->backupDir, 0755, true);
        $relative = substr($path, strlen($this->scanDir) + 1);
        $backupPath = $this->backupDir . '/' . $relative;
        $backupDir = dirname($backupPath);
        if (!is_dir($backupDir)) mkdir($backupDir, 0755, true);
        copy($path, $backupPath);
    }
    
    private function log($msg) {
        $entry = date('[Y-m-d H:i:s] ') . $msg . PHP_EOL;
        file_put_contents($this->logFile, $entry, FILE_APPEND);
        error_log($entry);
    }
    
    private function isWhitelisted($path) {
        foreach ($this->whitelist as $wl) {
            if (strpos($path, $this->scanDir . '/' . $wl) === 0) return true;
        }
        return false;
    }
    
    private function writeLog() {
        $summary = "掃描完成: 文件 {$this->results['scanned']}, 感染 {$this->results['infected']}, 清理 {$this->results['cleaned']}, 錯誤 {$this->results['errors']}";
        $this->log($summary);
    }
}

// 使用示例
$scanner = new WebShellScanner('/var/www/html');
$results = $scanner->scan();

// 僅報告,不自動清理
print_r($results);

// 如需清理,先干運行確認
// $scanner->clean(true);  // dry run
// $scanner->clean(false); // 實際清理
?>

常見 WebShell 特徵分類

類型特徵示例危害等級
單行木馬<?php @eval($_POST['x']);?>極高
大馬功能文件管理、命令執行、資料庫操作、端口掃描極高
編碼混淆Base64、gzdeflate、str_rot13、十六進制、Unicode高
偽裝文件命名為 config.php、functions.php、wp-load.php 等高
圖片馬正常圖片末尾追加 PHP 代碼,配合解析漏洞執行中
掛馬跳轉JS 跳轉、iframe 隱藏、document.write 注入中
挖礦腳本CoinHive、XMRig 等 JS/WebAssembly 挖礦代碼中

多層防護體系建議

1. 代碼層面

  • 禁用危險函數:disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,pcntl_exec
  • 開啟 open_basedir 限制文件訪問範圍
  • 關閉 allow_url_fopen、allow_url_include
  • 使用 suhosin 或 snuffleupagus 等安全擴展

2. 文件系統層面

  • 目錄權限最小化:目錄 755,文件 644,上傳目錄禁用執行權限
  • 上傳目錄配置 .htaccess 或 Nginx 禁用 PHP 解析
  • 核心文件只讀屬性:chattr +i 關鍵文件
  • 定期完整性檢查:對比文件哈希值(如 AIDE、Tripwire)

3. 監控與應急

  • 部署 WAF(ModSecurity、OpenResty+Lua)攔截攻擊請求
  • 日誌審計:Nginx/Apache 訪問日誌、PHP 錯誤日誌、審計日誌
  • 文件變更監控:inotifywait、auditd 實時監控關鍵目錄
  • 定期掃描:每日自動運行掃描腳本,結果郵件/企業微信告警
  • 應急預案:隔離感染主機 → 封鎖攻擊 IP → 從乾淨備份恢復 → 溯源分析 → 加固防護

應急響應檢查清單

# 1. 確認受影響範圍
find /var/www -name "*.php" -newer /tmp/reference_time -ls

# 2. 查找可疑文件
grep -r "eval.*base64_decode" /var/www --include="*.php"
grep -r "assert.*\\$_\(GET\|POST\)" /var/www --include="*.php"
find /var/www -name "*.php" -size +100k -exec ls -lh {} \;

# 3. 檢查異常進程
ps aux | grep -E "(php|perl|python).*[0-9]{1,3}\.[0-9]{1,3}"

# 4. 檢查計劃任務
crontab -l; cat /etc/crontab; ls /etc/cron.*

# 5. 檢查網絡連接
netstat -antp | grep :80
ss -antp | grep ESTAB

核心原則:自動清理只能作為輔助手段,核心防線是預防(代碼審計、權限控制、WAF 防護、定期備份)。一旦發現入侵,優先從乾淨備份恢復,而非依賴清理腳本。所有清理操作必須先備份、再干運行、最後人工確認執行。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved