红穆笔记
首頁 程序筆記 pbootcms官方文件與網站文件對比,檢測異常文件
程序筆記 PbootCms

pbootcms官方文件與網站文件對比,檢測異常文件

pbootcms官方文件与网站文件对比,检测异常文件

* PbootCMS 木馬掃描與文件完整性檢查腳本
* 用途:對比當前網站 PHP 文件與官方版本,找出多餘/被篡改的 PHP 文件
* 使用方法:將本文件命名爲 check_malware.php 並放置在網站根目錄,通過瀏覽器訪問或命令行運行
* 安全建議:運行後請及時刪除本腳本,避免被惡意利用

<?php
/**
 * PbootCMS 文件完整性檢查腳本(自動排除官方基準目錄)
 * 功能:對比當前網站 PHP 文件與官方版本,排除官方目錄本身,生成 HTML 報告
 * 使用方法:php check_malware.php
 * 安全建議:運行後立即刪除本腳本
 */

// ========== 配置區域 ==========
$CONFIG = [
    'official_dir'      => __DIR__ . '/doc/PbootCMS-3.2.13', // 官方版本解壓路徑
    'current_dir'       => __DIR__,                           // 當前網站根目錄
    'exclude_dirs'      => ['runtime', 'cache', 'temp'],     // 額外跳過的一級目錄(相對路徑首段)
    'html_output_file'  => __DIR__ . '/security_report.html', // HTML 報告保存路徑
    'show_diff'         => true,                             // 顯示文件差異
];

// ========== 安全:禁止外網訪問 ==========
if (php_sapi_name() !== 'cli') {
    $allowed = ['127.0.0.1', '::1'];
    if (!in_array($_SERVER['REMOTE_ADDR'], $allowed)) {
        die('Access denied. Please run this script from command line or localhost.');
    }
}

// ========== 核心函數 ==========

/**
 * 遞歸獲取目錄下所有 PHP 文件的相對路徑及元信息
 * @param string $baseDir        掃描的基礎目錄
 * @param array  $excludeDirs    排除的一級目錄名(僅匹配相對路徑首段)
 * @param array  $excludePrefixes 排除的路徑前綴(完整相對路徑前綴,如 'doc/PbootCMS-3.2.13')
 */
function getPhpFilesMap($baseDir, $excludeDirs = [], $excludePrefixes = []) {
    $map = [];
    $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($baseDir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    foreach ($iterator as $file) {
        if (!$file->isFile() || $file->getExtension() !== 'php') {
            continue;
        }
        $realPath = $file->getRealPath();
        $relativePath = str_replace($baseDir, '', $realPath);
        // 檢查一級目錄排除
        $firstDir = strtok($relativePath, DIRECTORY_SEPARATOR);
        if (in_array($firstDir, $excludeDirs)) {
            continue;
        }
        // 檢查前綴排除(如官方目錄)
        $skip = false;
        foreach ($excludePrefixes as $prefix) {
            if (strpos($relativePath, $prefix . DIRECTORY_SEPARATOR) === 0 || $relativePath === $prefix) {
                $skip = true;
                break;
            }
        }
        if ($skip) {
            continue;
        }
        $map[$relativePath] = [
            'md5'   => md5_file($realPath),
            'size'  => $file->getSize(),
            'mtime' => date('Y-m-d H:i:s', $file->getMTime()),
        ];
    }
    return $map;
}

/**
 * 對比兩個文件映射,返回差異分類
 */
function compareFiles($official, $current) {
    $extra = $modified = $missing = [];
    foreach ($current as $path => $info) {
        if (isset($official[$path])) {
            if ($info['md5'] !== $official[$path]['md5']) {
                $modified[] = $path;
            }
        } else {
            $extra[] = $path;
        }
    }
    foreach ($official as $path => $info) {
        if (!isset($current[$path])) {
            $missing[] = $path;
        }
    }
    return [$extra, $modified, $missing];
}

/**
 * 純 PHP 逐行差異對比(基於 LCS)
 */
function computeDiff($oldLines, $newLines) {
    if (!is_array($oldLines)) $oldLines = explode("\n", $oldLines);
    if (!is_array($newLines)) $newLines = explode("\n", $newLines);
    
    $matrix = [];
    $maxlen = 0;
    $omax = $nmax = 0;
    foreach ($oldLines as $oindex => $ovalue) {
        $nkeys = array_keys($newLines, $ovalue);
        foreach ($nkeys as $nindex) {
            $matrix[$oindex][$nindex] = isset($matrix[$oindex-1][$nindex-1]) ? $matrix[$oindex-1][$nindex-1] + 1 : 1;
            if ($matrix[$oindex][$nindex] > $maxlen) {
                $maxlen = $matrix[$oindex][$nindex];
                $omax = $oindex + 1 - $maxlen;
                $nmax = $nindex + 1 - $maxlen;
            }
        }
    }
    if ($maxlen == 0) {
        return [
            ['op' => 'del', 'lines' => $oldLines, 'old_ln' => 1, 'new_ln' => 0],
            ['op' => 'add', 'lines' => $newLines, 'old_ln' => 0, 'new_ln' => 1]
        ];
    }
    $oldPrefix = array_slice($oldLines, 0, $omax);
    $newPrefix = array_slice($newLines, 0, $nmax);
    $oldSuffix = array_slice($oldLines, $omax + $maxlen);
    $newSuffix = array_slice($newLines, $nmax + $maxlen);
    $common = array_slice($newLines, $nmax, $maxlen);
    
    $result = [];
    if (!empty($oldPrefix) || !empty($newPrefix)) {
        $result = array_merge($result, computeDiff($oldPrefix, $newPrefix));
    }
    if (!empty($common)) {
        $result[] = ['op' => 'same', 'lines' => $common, 'old_ln' => $omax+1, 'new_ln' => $nmax+1];
    }
    if (!empty($oldSuffix) || !empty($newSuffix)) {
        $result = array_merge($result, computeDiff($oldSuffix, $newSuffix));
    }
    return $result;
}

/**
 * 獲取兩個文件的差異(行數組)
 */
function getFileDiff($officialFile, $currentFile) {
    if (!is_readable($officialFile) || !is_readable($currentFile)) {
        return false;
    }
    $oldContent = file_get_contents($officialFile);
    $newContent = file_get_contents($currentFile);
    $oldLines = explode("\n", $oldContent);
    $newLines = explode("\n", $newContent);
    return computeDiff($oldLines, $newLines);
}

/**
 * 生成完整的 HTML 報告字符串
 */
function generateHtmlReport($extra, $modified, $missing, $officialDir, $currentDir, $showDiff, $excludedPrefixes) {
    $html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>PbootCMS 文件完整性檢查報告</title>';
    $html .= '<style>
        body{font-family:system-ui,sans-serif;margin:20px;line-height:1.6;background:#fafafa;}
        .container{max-width:1400px;margin:0 auto;background:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,0.1);}
        h1{font-size:28px;border-bottom:2px solid #eee;padding-bottom:10px;}
        .bad{color:#d32f2f;} .good{color:#2e7d32;} .warn{color:#ed6c02;}
        table{border-collapse:collapse;width:100%;margin:15px 0;}
        td,th{border:1px solid #ddd;padding:8px 12px;text-align:left;vertical-align:top;}
        th{background:#f5f5f5;}
        .file-path{font-family:monospace;word-break:break-all;}
        details{margin:8px 0;}
        summary{cursor:pointer;color:#0066cc;font-weight:bold;}
        .diff-box{background:#f8f8f8;border:1px solid #ddd;padding:10px;overflow:auto;max-height:400px;font-size:13px;font-family:monospace;}
        .diff-box .same{color:#333;}
        .diff-box .del{color:#d32f2f;background:#ffebee;}
        .diff-box .add{color:#2e7d32;background:#e8f5e9;}
        .count-badge{font-size:14px;font-weight:normal;margin-left:10px;background:#eee;padding:2px 10px;border-radius:12px;}
        .footer{margin-top:30px;border-top:1px solid #eee;padding-top:15px;font-size:14px;color:#666;}
        .exclude-info{background:#f0f0f0;padding:8px 15px;border-radius:4px;margin:10px 0;}
    </style></head><body>
    <div class="container">
        <h1>🔍 PbootCMS 文件完整性檢查報告</h1>
        <p><strong>生成時間:</strong>' . date('Y-m-d H:i:s') . '</p>
        <p><strong>官方版本路徑:</strong>' . htmlspecialchars($officialDir) . '<br>
        <strong>當前網站路徑:</strong>' . htmlspecialchars($currentDir) . '</p>
        <div class="exclude-info"><strong>已排除的路徑前綴:</strong>' . htmlspecialchars(implode(', ', $excludedPrefixes)) . '</div>';

    // 額外文件
    $html .= '<h2 class="warn">⚠️ 額外文件(官方版本中不存在) <span class="count-badge">共 ' . count($extra) . ' 個</span></h2>';
    if ($extra) {
        $html .= '<table><tr><th>文件路徑</th><th>文件大小</th><th>修改時間</th><th>建議操作</th></tr>';
        foreach ($extra as $file) {
            $filePath = $currentDir . DIRECTORY_SEPARATOR . $file;
            $size = is_file($filePath) ? number_format(filesize($filePath)) . ' B' : 'N/A';
            $mtime = is_file($filePath) ? date('Y-m-d H:i:s', filemtime($filePath)) : 'N/A';
            $html .= "<tr><td class='file-path'>$file</td><td>$size</td><td>$mtime</td><td>🔍 人工覈查,極可能是木馬或非法文件</td></tr>";
        }
        $html .= '</table>';
    } else {
        $html .= '<p class="good">✅ 未發現多餘 PHP 文件</p>';
    }

    // 被篡改的文件
    $html .= '<h2 class="bad">🔧 被篡改的文件(與官方版本內容不同) <span class="count-badge">共 ' . count($modified) . ' 個</span></h2>';
    if ($modified) {
        $html .= '<table><tr><th>文件路徑</th><th>文件大小</th><th>修改時間</th><th>差異詳情</th></tr>';
        foreach ($modified as $file) {
            $officialFile = $officialDir . DIRECTORY_SEPARATOR . $file;
            $currentFile  = $currentDir . DIRECTORY_SEPARATOR . $file;
            $size = is_file($currentFile) ? number_format(filesize($currentFile)) . ' B' : 'N/A';
            $mtime = is_file($currentFile) ? date('Y-m-d H:i:s', filemtime($currentFile)) : 'N/A';
            $html .= "<tr><td class='file-path'>$file</td><td>$size</td><td>$mtime</td><td>";
            if ($showDiff) {
                $diff = getFileDiff($officialFile, $currentFile);
                if ($diff) {
                    $html .= "<details><summary>📄 顯示差異(行號標記)</summary>";
                    $html .= "<div class='diff-box'>";
                    $oldLine = 1; $newLine = 1;
                    foreach ($diff as $chunk) {
                        switch ($chunk['op']) {
                            case 'same':
                                foreach ($chunk['lines'] as $line) {
                                    $html .= "<span class='same'>  " . str_pad($oldLine, 4, ' ', STR_PAD_LEFT) . "  " . htmlspecialchars($line) . "</span><br>";
                                    $oldLine++; $newLine++;
                                }
                                break;
                            case 'del':
                                foreach ($chunk['lines'] as $line) {
                                    $html .= "<span class='del'>- " . str_pad($oldLine, 4, ' ', STR_PAD_LEFT) . "  " . htmlspecialchars($line) . "</span><br>";
                                    $oldLine++;
                                }
                                break;
                            case 'add':
                                foreach ($chunk['lines'] as $line) {
                                    $html .= "<span class='add'>+ " . str_pad($newLine, 4, ' ', STR_PAD_LEFT) . "  " . htmlspecialchars($line) . "</span><br>";
                                    $newLine++;
                                }
                                break;
                        }
                    }
                    $html .= "</div></details>";
                } else {
                    $html .= "⚠️ 無法讀取文件內容";
                }
            } else {
                $html .= "差異對比已禁用";
            }
            $html .= "</td></tr>";
        }
        $html .= '</table>';
    } else {
        $html .= '<p class="good">✅ 無核心文件被篡改</p>';
    }

    // 缺失文件
    $html .= '<h2 class="warn">📁 缺失的文件(官方存在但當前網站丟失) <span class="count-badge">共 ' . count($missing) . ' 個</span></h2>';
    if ($missing) {
        $html .= '<table><tr><th>文件路徑</th><th>官方大小</th><th>建議操作</th></tr>';
        foreach ($missing as $file) {
            $officialFile = $officialDir . DIRECTORY_SEPARATOR . $file;
            $size = is_file($officialFile) ? number_format(filesize($officialFile)) . ' B' : 'N/A';
            $html .= "<tr><td class='file-path'>$file</td><td>$size</td><td>📥 從官方版本補回</td></tr>";
        }
        $html .= '</table>';
    } else {
        $html .= '<p class="good">✅ 無核心文件缺失</p>';
    }

    $html .= '<div class="footer">';
    $html .= '<p>📌 <strong>安全建議</strong></p><ul>';
    $html .= '<li>「額外文件」中的 <code>template/</code> 或 <code>static/uploads/</code> 下如有 PHP 文件,<strong>極可能是木馬後門</strong>,應立即刪除。</li>';
    $html .= '<li>「被篡改的文件」若確認非自己修改,請務必用官方版本覆蓋,並檢查差異內容是否包含惡意代碼。</li>';
    $html .= '<li>修復後請立即更改服務器密碼、檢查系統日誌、更新到最新版 PbootCMS。</li>';
    $html .= '</ul>';
    $html .= '<p>報告生成時間:' . date('Y-m-d H:i:s') . ' | 腳本僅作參考,請結合實際情況處理。</p>';
    $html .= '</div></div></body></html>';

    return $html;
}

/**
 * 輸出命令行文本報告
 */
function renderCliReport($extra, $modified, $missing) {
    echo "\n========== PbootCMS 文件完整性檢查報告 ==========\n";
    echo "\n🔴 額外文件(官方版本中不存在): " . count($extra) . " 個\n";
    if ($extra) { foreach ($extra as $file) echo "  + $file\n"; } else echo "  ✅ 無\n";

    echo "\n🟠 被篡改的文件(內容不同): " . count($modified) . " 個\n";
    if ($modified) { foreach ($modified as $file) echo "  * $file\n"; } else echo "  ✅ 無\n";

    echo "\n🔵 缺失的文件(官方存在但當前丟失): " . count($missing) . " 個\n";
    if ($missing) { foreach ($missing as $file) echo "  - $file\n"; } else echo "  ✅ 無\n";
    echo "\n==================================================\n";
}

// ========== 主流程 ==========

if (!is_dir($CONFIG['official_dir'])) {
    die("錯誤:官方版本目錄不存在,請將 PbootCMS 官方完整包解壓到 " . $CONFIG['official_dir'] . "\n");
}

// 計算官方目錄相對於當前目錄的相對路徑(用於排除)
$officialRelPath = str_replace($CONFIG['current_dir'] . DIRECTORY_SEPARATOR, '', $CONFIG['official_dir']);
if ($officialRelPath == $CONFIG['official_dir']) {
    // 如果官方目錄不在當前目錄下(絕對路徑不同),則不自動排除
    $officialRelPath = null;
}
$excludePrefixes = [];
if ($officialRelPath) {
    $excludePrefixes[] = $officialRelPath;
}
// 可額外手動添加排除前綴(例如用戶自定義)
// $excludePrefixes[] = 'some/other/dir';

echo "正在掃描 PHP 文件,請稍候...\n";
echo "排除前綴:" . implode(', ', $excludePrefixes) . "\n";

// 掃描官方目錄(不排除自己,因爲它是基準)
$officialFiles = getPhpFilesMap($CONFIG['official_dir'], $CONFIG['exclude_dirs'], []);
// 掃描當前目錄,排除官方目錄及其他指定前綴
$currentFiles = getPhpFilesMap($CONFIG['current_dir'], $CONFIG['exclude_dirs'], $excludePrefixes);

list($extra, $modified, $missing) = compareFiles($officialFiles, $currentFiles);

// 生成 HTML 報告並保存爲文件
$htmlContent = generateHtmlReport(
    $extra, $modified, $missing,
    $CONFIG['official_dir'],
    $CONFIG['current_dir'],
    $CONFIG['show_diff'],
    $excludePrefixes
);

$outputFile = $CONFIG['html_output_file'];
if (file_put_contents($outputFile, $htmlContent) !== false) {
    echo "✅ HTML 報告已生成:{$outputFile}\n";
} else {
    echo "❌ 寫入報告文件失敗,請檢查目錄權限。\n";
}

// 命令行下輸出簡潔文本報告
renderCliReport($extra, $modified, $missing);

echo "\n掃描完成。請打開 {$outputFile} 查看詳細差異。\n";
echo "建議及時刪除本腳本以免被濫用。\n";

 

微信赞赏

微信

支付宝赞赏

支付寶

✍️ 作者: 紅穆

網站管理員 · 感謝閱讀,更多精彩內容敬請關注

作者主頁 查看主頁 →

相關文章

Pbootcms幻灯片代码

Pbootcms幻燈片代碼 程序筆記 PbootCms

幻燈片調用例子:{pboot:slide num=3 gid=1} &lt;a href=&quot;[slide:link]&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;[slide:src]&quot; alt=&quot;[slid…
👁 179
pbootcms 网站留言功能的添加

pbootcms 網站留言功能的添加 程序筆記 PbootCms

留言板標籤適用範圍:全站任意地方均可使用標籤作用:用於用戶提交留言和調取留言記錄1、留言提交表單&lt;form action=&quot;{pboot:msgaction}&quot; method=&quot;post&quot;&gt; 聯繫人:&lt;input type=&quot;tex…
👁 174
pbootcms引入公共文件代码

pbootcms引入公共文件代碼 程序筆記 PbootCms

1、模板文件嵌套引用{include file=***.html}使用說明:可以嵌套使用,如:index.html 嵌套一個head.html,同時head.html中嵌套comm.html支持使用子目錄,如:{include file=comm/*.html}2、時間格式化標籤style=*如:內…
👁 235

推薦閱讀

半导体电子元器件类网站模板 1016

半導體電子元器件類網站模板 1016 實用收藏 易優模板

一款針對半導體與電子元器件類企業的eyoucms網站模板。設計風格科技精密,能夠展示半導體產品、電子元器件及技術方案。有助於半導體企業在線上展示產品,吸引電子行業客戶。模板展示 安裝說明 網站後臺:/login.php 賬號:admin 密碼:admin 相關文章易優CMS安裝常見問題總結易優CMS…
👁 50
智能车场道闸设备企业网站模板 0421

智能車場道閘設備企業網站模板 0421 實用收藏 易優模板

一款針對智能車場與道閘設備企業的eyoucms網站模板。設計風格科技安全,能夠展示停車場系統、道閘設備、智能管理解決方案及工程案例。有助於智能安防企業在線上展示產品,吸引物業與商業客戶。模板展示 安裝說明 網站後臺:/login.php 賬號:admin 密碼:admin 相關文章易優CMS安裝常見…
👁 54
(自适应手机版)响应式黑色炫酷建筑装饰设计类pbootcms模板 HTML5装修设计公司网站源码下载 0458

(自適應手機版)響應式黑色炫酷建築裝飾設計類pbootcms模板 HTML5裝修設計公司網站源碼下載 0458 實用收藏 pbootcms模板

本套自適應手機版的電子元件與電路板PbootCMS網站模板。設計風格科技精密,能夠展示電子元器件、PCB板等產品。有助於電子元件製造或貿易企業在線上展示產品,拓展電子行業客戶。模板展示 安裝說明 網站後臺:/admin.php 賬號:admin 密碼:admin 解壓密碼:www.4s5.cn相關文…
👁 48
响应式AI数据服务采集网站模板 1017

響應式AI數據服務採集網站模板 1017 實用收藏 易優模板

此套eyoucms響應式模板適用於AI數據服務與採集行業,設計風格科技前沿,能夠展示數據採集、AI標註、數據處理服務及技術實力。有助於數據服務公司在線上吸引AI企業客戶。模板展示 安裝說明 網站後臺:/login.php 賬號:admin 密碼:admin 相關文章易優CMS安裝常見問題總結易優CM…
👁 48
响应式时尚成衣职业装网站模板 0422

響應式時尚成衣職業裝網站模板 0422 實用收藏 易優模板

此套eyoucms響應式模板適用於時尚成衣與職業裝行業,設計風格時尚專業,能夠展示成衣產品、職業裝定製、品牌形象及企業團購服務。有助於服裝品牌在線上展示產品,吸引企業及個人客戶。模板展示 安裝說明 網站後臺:/login.php 賬號:admin 密碼:admin 相關文章易優CMS安裝常見問題總結…
👁 47