Hongmu Notes
Home Program Notes Compare pbootcms official files with website files to detect abnormal files
Program Notes PbootCms

Compare pbootcms official files with website files to detect abnormal files

Compare pbootcms official files with website files to detect abnormal files

* PbootCMS Trojan scanning and file integrity checking script
* Purpose: Compare the current website PHP files with the official version to find redundant/tampered PHP files
* Usage: Name this file check_malware.php and place it in the root directory of the website, access it through a browser or run it from the command line
* Security advice: Please delete this script promptly after running it to avoid malicious use.

<?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";

 

微信赞赏

WeChat

支付宝赞赏

Alipay

✍️ Author: Hong Mu

webmaster · Thanks for reading, stay tuned for more exciting content

Author homepage View home page →

Related articles

Addition of message function to pbootcms website

Addition of message function to pbootcms website Program Notes PbootCms

Scope of application of message board tags: can be used anywhere on the entire site Tag function: used for users to submit messages and retrieve message records 1. Message submission form<form action="{pboot:msgaction}" method="post"> Contact person:<input type="tex…
👁 174
pbootcms introduces public file code

pbootcms introduces public file code Program Notes PbootCms

1. Template file nested reference {include file=***.html} Instructions for use: It can be used nested, such as: index.html nests a head.html, and nested comm.html in head.html supports the use of subdirectories, such as: {include file=comm/*.html} 2. Time formatting tag style=*, such as: within...
👁 235

Recommended reading

Semiconductor electronic components website template 1016

Semiconductor electronic components website template 1016 Practical Collection Yiyou template

An eyouCMS website template designed specifically for semiconductor and electronic component manufacturers. Its modern, high-tech design is ideal for showcasing semiconductor products, electronic components, and technical solutions. This template helps semiconductor companies present their products online and attract customers from the electronics industry. Template Overview | Installation Instructions | Website Backend: /login.php | Username: admin | Password: admin | Related Articles: Summary of Common Installation Issues for YouYouCMS | YouYouCMS...
👁 48
Smart parking lot barrier equipment corporate website template 0421

Smart parking lot barrier equipment corporate website template 0421 Practical Collection Yiyou template

An eyoucms website template for smart parking lots and gate equipment companies. The design style is technological and safe, and can display parking systems, gate equipment, intelligent management solutions and engineering cases. It helps smart security companies display their products online and attract property and commercial customers. Template display Installation instructions Website backend: /login.php Account: admin Password: admin Related articles Yiyou CMS installation common...
👁 53
(Adaptive mobile version) Responsive electronic products English foreign trade website template 1018

(Adaptive mobile version) Responsive electronic products English foreign trade website template 1018 Practical Collection pbootcms Template

A responsive English foreign trade PbootCMS website template for electronic products, supporting PC and WAP. The design style is international and simple, suitable for electronic product foreign trade companies to display product lines and brand strength. It helps Chinese electronic product manufacturing companies display their image in the global market. Template display Installation instructions Website backend:/admin.php Account: admin Password: admin Unzip password: www.4s5.cn…
👁 49
(Adaptive Mobile Version) Responsive Black Cool Architectural Decoration Design PBootCMS Template – Download HTML5 Interior Design Company Website Source Code – 0458

(Adaptive Mobile Version) Responsive Black Cool Architectural Decoration Design PBootCMS Template – Download HTML5 Interior Design Company Website Source Code – 0458 Practical Collection pbootcms Template

This adaptive mobile-friendly e-component and PCB website template by PbootCMS is designed with a modern, precision-oriented style, ideal for showcasing electronic components, PCB boards, and other products. It enables electronics manufacturing or trading enterprises to display their products online and expand their customer base within the electronics industry. Template Display | Installation Instructions | Website Backend: /admin.php | Username: admin | Password: admin | Extraction Password: www.4s5.cn | Related Articles...
👁 47
Responsive AI Data Service Collection Website Template 1017

Responsive AI Data Service Collection Website Template 1017 Practical Collection Yiyou template

This EyouCMS responsive template is ideal for the AI data services and data collection industry. Its cutting-edge design effectively showcases data collection, AI annotation, data processing services, and technical expertise, helping data service providers attract AI enterprise clients online. Template Display | Installation Instructions | Website Backend: /login.php | Username: admin | Password: admin | Related Articles: Summary of Common Installation Issues for EyouCMS | EyouCMS...
👁 48
Responsive ready-to-wear professional clothing website template – 0422

Responsive ready-to-wear professional clothing website template – 0422 Practical Collection Yiyou template

This EyouCMS responsive template is ideal for the ready-made clothing and professional wear industries. Its stylish and professional design makes it perfect for showcasing ready-made garments, custom professional wear solutions, brand identity, and corporate group purchasing services. It helps clothing brands showcase their products online and attract both corporate and individual customers. Template Overview | Installation Instructions | Website Backend: /login.php | Username: admin | Password: admin | Related Articles: Summary of Common Installation Issues for EyouCMS...
👁 47