網站被植入惡意腳本(如隱藏 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();
?>
<?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); // 實際清理
?>
| 類型 | 特徵示例 | 危害等級 |
|---|---|---|
| 單行木馬 | <?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 挖礦代碼 | 中 |
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,pcntl_execopen_basedir 限制文件訪問範圍allow_url_fopen、allow_url_includesuhosin 或 snuffleupagus 等安全擴展.htaccess 或 Nginx 禁用 PHP 解析chattr +i 關鍵文件# 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 防護、定期備份)。一旦發現入侵,優先從乾淨備份恢復,而非依賴清理腳本。所有清理操作必須先備份、再干運行、最後人工確認執行。