---
title: ThinkPHP 项目文件完整性监控：用 Shell 脚本对抗黑帽 SEO 注入
description: 公司官网遭黑帽 SEO 注入，&lt;code&gt;index.php&lt;/code&gt; 被植入远控代码，爬虫被喂假页面、真实用户被重定向。清理之后需
author: chaihongjun
category: backend
published: 2026-07-14
url: https://chaihongjun.me/backend/57.html
---

# ThinkPHP 项目文件完整性监控：用 Shell 脚本对抗黑帽 SEO 注入

## 1. 背景

公司官网（ThinkPHP 5.0）曾遭遇黑帽 SEO 注入。攻击者在 MDBLOCK0 中插入了 base64 编码的远控地址，逻辑如下：

-
检测 User-Agent：如果是 Baiduspider / Sogou / 360 / Yisou 等爬虫，返回一个完全不同的假页面

-
来自搜索引擎的真实用户点击搜索结果 → 被重定向到垃圾站

这种注入在文件层面极其隐蔽——网页在浏览器中显示完全正常（非爬虫请求不受影响），只有从搜索引擎过来的流量才会中招。等到自然搜索流量大幅下滑才发现已经被黑了很久。

虽然恶意代码已经清理干净，但一个问题摆在面前：下次怎么第一时间知道文件被改了？

## 2. 需求

针对这次教训，我们列出了几条刚需：

-
不侵入项目代码 — 不能往 PHP 项目里加文件完整性检查的逻辑，避免引入新的风险面

-
零依赖 — 不能用 Tripwire / AIDE / OSSEC 等需要安装的第三方工具，生产环境能不动就不动

-
不需要监控服务 — 不接入收费 SaaS 监控，也不需要单独搭一套监控平台

-
每天检查一次就够了 — 黑帽 SEO 不会频繁注入，太高的检测频率没有意义

-
有变化时登录能看见 — 不需要单独发邮件或短信，SSH 进服务器的时候直接看到警告

总结成一句话："每天跑一遍、文件变了就警告、登录就能看见"。

## 3. 方案选型

满足上述需求，最简单的方案就是 Shell 脚本 + cron + MDBLOCK1。

核心命令只有三条：
MDBLOCK2
为什么不用其他工具：

| 方案 | 问题 |
| --- | --- ||
| Tripwire / AIDE | 需要安装、维护策略文件、学习成本高 |
| ThinkPHP CLI 命令 | 需要加载框架，依赖 PHP 环境，比纯 Shell 慢 |
| Git diff | 生产环境不一定有 Git 历史，且 .gitignore 外的文件可能漏检 |
| inotify 实时监控 | 资源开销大，黑帽 SEO 不需要秒级发现 |
| find + md5sum + diff | 零依赖，3 条命令，70 行脚本搞定 |

选 Shell 的另一个好处：部署新代码后更新基线只需一条命令，不需要重建索引或等待同步。

## 4. 检测原理

### snapshot — 建立基线

MDBLOCK3

### check — 比对检查

MDBLOCK4
如果文件被篡改（哈希变化），diff 中会同时出现一行 MDBLOCK5（旧哈希）和一行 MDBLOCK6（新哈希），脚本统一归类为 MDBLOCK7。

### 登录告警联动

MDBLOCK8 下的脚本在每次 SSH 登录时自动执行。我们放置的 MDBLOCK9 读取 MDBLOCK10：有内容就展示红色警告，flag 不存在或为空则不输出任何内容。
MDBLOCK11

## 5. 涉及的脚本

整个监控体系由三个文件组成：

| 文件 | 用途 |
| --- | --- ||
| MDBLOCK12 | 主脚本：MDBLOCK13 建立基线 / MDBLOCK14 比对检查 |
| MDBLOCK15 | 部署到 MDBLOCK16，SSH 登录时自动展示告警 |
| MDBLOCK17 | 使用文档 |

### 主脚本核心逻辑

MDBLOCK18

## 6. 部署步骤

### 第一步：部署主脚本

MDBLOCK19

### 第二步：建立基线

MDBLOCK20
基线文件存储在 MDBLOCK21，权限 root:root 700，Web 用户无法写入。

### 第三步：部署登录警告

MDBLOCK22

### 第四步：添加计划任务

MDBLOCK23

### 验证

MDBLOCK24

## 7. 输出示例

### 正常情况

所有文件无变化时，cron 凌晨 3 点静默执行，登录时无任何提示——无事发生即是最好的消息。

### 有文件被篡改时

MDBLOCK25

### 部署新代码后更新基线

MDBLOCK26

## 8. 一些细节

### 为什么会有 [缺失] 文件

MDBLOCK27 目录下的日志文件不在扫描范围内，但如果应用程序在运行时生成或删除了缓存文件（且路径未被正确排除），diff 会报 [缺失] 或 [变更]。排查时将这类路径加入 MDBLOCK28 排除即可。

### 维护成本

这套脚本上线后几乎零维护。唯一需要操作的是：部署新代码后跑一次 snapshot 更新基线。如果把这个动作也忘了，check 会报大量 [变更]，此时跑一次 snapshot 即可恢复。

### 反过来想

70 行 Shell 脚本解决了一个可能让网站被降权、搜索流量归零的安全问题。不是所有安全方案都需要微服务架构或昂贵的商业产品，有时候 MDBLOCK29 就够了。

本文涉及的脚本

![](https://chaihongjun.me/public/static/js/ueditor/dialogs/attachment/fileTypeImages/icon_txt.gif)[etc-profile-d-thinkphp-warning.txt](https://chaihongjun.me/public/uploads/files/20260714/1783992925855518.txt)

![](https://chaihongjun.me/public/static/js/ueditor/dialogs/attachment/fileTypeImages/icon_txt.gif)[thinkphp-integrity.txt](https://chaihongjun.me/public/uploads/files/20260714/1783993968226744.txt)

以上脚本部署的时候注意修改后缀

---

> 本文首发于 [chaihongjun.me | 柴宏俊 web 技术学习笔记](https://chaihongjun.me/backend/57.html)，采用 CC BY-NC-ND 4.0 协议，转载请保留出处与链接。
