moh3ndotnet/joomla-shell-scanner
GitHub: moh3ndotnet/joomla-shell-scanner
一款零依赖的单文件 Joomla Web Shell 扫描与隔离工具,通过特征、结构、位置和基线四重检测机制精准发现并安全处置被入侵站点中的后门。
Stars: 0 | Forks: 0
# Joomla Shell Scanner
**[فارسی](README.fa.md)**
一个单一的 PHP 文件,用于在被黑客入侵的 Joomla 网站上查找 web shell 和后门,准确向您展示它发现了什么以及为什么,并根据您的指示进行隔离。
无需安装,无需依赖,无需 composer,也没有自己的数据库。只需上传一个文件,在浏览器中打开它,阅读报告,然后删除该文件。
```
https://your-site.com/joomla-shell-scanner.php
```
它是在 2026 年 7 月的一次 SP Page Builder 图标字体入侵事件中编写的,但其检测机制并不局限于该事件——大多数规则都是基于结构的,因此它们也能捕获作者从未见过的 shell。
## 目录
- [为什么需要另一个扫描器](#why-another-scanner)
- [快速入门](#quick-start)
- [它能检测什么](#what-it-detects)
- [它如何判定:CONFIRMED / HEURISTIC / REVIEW](#how-it-decides)
- [隔离 —— 绝不直接删除](#quarantine)
- [加固上传文件夹](#hardening-upload-folders)
- [数据库审查](#database-review)
- [基线清单](#baseline-manifests)
- [同时扫描多个站点](#scanning-several-sites-at-once)
- [命令行](#command-line)
- [在 Web 服务器阻止 PHP 文件时通过 cron 运行](#running-it-from-cron)
- [安全说明 —— 必读](#security-notes)
- [清理之后](#after-the-cleanup)
- [局限性](#limitations)
- [环境要求](#requirements)
## 为什么需要另一个扫描器
对于被黑客入侵的 Joomla 站点,大多数建议都是“与全新下载的文件进行比较”。这是一个好建议,但也仅此而已:它无法检测 `configuration.php`,无法检测您自己的扩展程序正常写入的文件,也无法检测上两级目录中在您的图片文件夹中重新启用 PHP 的 `.htaccess` 文件。
此工具结合了四种独立的方法,使得 shell 必须同时躲避所有这些方法才能隐藏:
| 方法 | 会被哪种方式绕过 | 是否被其他方法捕获 |
|---|---|---|
| 已知的哈希值和字符串 | 重命名、修改一个字节 | 结构、位置、基线 |
| 结构化代码规则 | 大量自定义混淆 | 启发式、基线 |
| 位置规则(出现不该出现 PHP 文件的地方) | 将 shell 放入代码文件夹 | 特征码、基线 |
| 与干净版本的基线差异 | 无——但单独使用会产生大量噪音 | 评分和分组,而不是直接输出 |
它报告的每一项都附带了被报告的原因,因此您可以对此提出异议。
## 快速入门
1. 下载 [`joomla-shell-scanner.php`](joomla-shell-scanner.php)。
2. 将其上传到您的 **网站根目录** —— 即包含 `configuration.php` 的文件夹。
文件名无关紧要;您可以随意命名。
3. 在浏览器中打开它。在您按下 **开始扫描** 之前,它不会从磁盘读取任何内容。
4. 阅读结果。勾选您想要移除的内容,然后按下隔离按钮。
5. 完成后按 **删除扫描器**。不要将其留在服务器上。
界面提供 **英语** 和 **فارسی** 版本 —— 切换按钮位于右上角。
两种语言下的路径、原因和哈希值都将保留为英文,因为它们是证据,并且最终会被粘贴到支持工单中。
## 它能检测什么
### 1. 已知的 Payload
31 个 payload 哈希值和 17 个内容字符串,每个都有一个名称,因此报告会告诉您正在看的是 *什么*,而不仅仅是“恶意软件”。其中包括:
`.icons.php` 凭据窃取器 · `filefuns.php` cookie/远程后门 · SPPB-RCE-42 shell 及其变种 · SeoMaster C2 代理 v1.3/v1.4 · X9 Tools 上传器 · MARIJUANA shell 家族 · LinkForge SEO 注入器 · WRST 凭据收集器 · PHP File Manager 1.4 后门 · tiny-file-manager shell · PNG 多语态 cookie 投放器 · SSI 命令执行和文件泄露 shell · 五个不同的攻击者 `.htaccess` 文件 · 一个伪装成 MIT 许可 curl 助手的远程包含加载器。
哈希值是结构规则之上的额外奖励,绝不是基础——改变一个字节就能绕过它们,而引擎的其余部分并不依赖于此。
### 2. 结构化代码规则
针对在重命名、重新哈希和表面修改后依然存在的 *特征* 的正则表达式:
- 作为函数调用的请求值 —— `$_GET['a']($_POST['b'])`
- 由字符转义序列拼接而成的超全局变量名 —— `${"\x5f\x47..."}`
- 隐藏在虚假 JSON 响应背后的后门,使该文件看起来像一个 API endpoint
- 对已解码或请求提供的数据执行 `eval` / `assert`
- 带有 `/e` 修饰符的 `preg_replace`
### 3. 位置规则
文件所处的位置本身就是一种证据。`images/` 下的 `.php` 文件之所以可疑,不是因为它的内容——而是因为它的存在。
- `media`、`images`、`tmp`、`cache`、`administrator/cache`、`logs`、`files` 中的可执行文件
- 上传文件夹中的 **空** PHP 文件 —— 这是一个写入探针,是部署 shell 之前的试探步骤
- 上传文件夹中的 `error_log` / `php_errorlog` —— **证明该文件夹中有东西被执行过**
- 多语态文件:`.css`、`.woff`、`.png`、`.json` ... 内部包含真实的 PHP 代码
- SP Page Builder 图标字体目录树(这是一个允许列表)下的任何非字体文件:真正的图标字体只有字体和样式表,没有其他东西
- 残留的 `tmp/builderCustomIcon_*` 和 `tmp/install_*` 解压目录
- 包含真实代码的 `index.php`,且不位于 Joomla 实际发布的三个核心位置(`/index.php`、`/administrator/index.php`、`/api/index.php`)以及模板文件夹之外
- 29 个已知的后门工具包文件名 —— `filefuns.php`、`adminfuns.php`、`chtmlfuns.php`、`epinyins.php`、`phpzipincs.php`、`siteheads.php` … — 加上三个有歧义的文件(`system_log.php`、`inputs.php`、`connects.php`),它们仅被报告为需要审查 (REVIEW)
### 4. `.htaccess` 篡改
- 任何通过 `AddType` / `AddHandler` / `SetHandler` / `Action` 行为原本服务器不会执行的扩展名重新启用 PHP 的配置
- 一个 **将后门文件名加入白名单** 的根 `.htaccess` —— 这正是编写此工具所针对的事件中使用的确切手法:拒绝所有 PHP,然后允许 `index.php` 加上入侵者自己的工具包,这会将所有者锁定在所有诊断工具之外,同时留下后门
被篡改的根 `.htaccess` 总是被报告为 REVIEW,绝不作为需要删除的内容:因为您的 SEF 规则就存在该文件中。
### 5. 启发式引擎
对仍未被识别的内容进行与名称和哈希无关的评分。
文件得分达到 **9 分** 或以上时将被报告。部分检测信号:
| 分数 | 信号 |
|---:|---|
| 7 | 由字符转义序列构建的超全局变量 |
| 7 | 作为函数调用的请求值 |
| 7 | 通过 `$GLOBALS['_GET']` 访问的超全局变量 —— 这是绕过基于 grep 扫描器的经典方法 |
| 7 | 三个或更多隐藏为十六进制字符串的 PHP 函数名(会被解码并与真实函数表进行核对,因此颜色字面量或 md5 不会计入) |
| 7 | `glob('/home/*')` —— 跨账户掠夺 |
| 6 | 四个或更多作为后备链的命令执行函数 |
| 6 | goto 展平的控制流 |
| 6 | 解码请求数据 |
| 6 | 自包含的上传表单 —— Joomla 通过 JForm 构建表单,绝不会使用纯粹手写的表单 |
| 5 | 将请求数据写入磁盘 |
| 5 | 搜寻 `configuration.php` / `wp-config.php` / `.env` |
| 4 | `eval` 家族调用 · 探测 `disable_functions` · 自我删除 · 在 `` / iframe 注入
数据库中的任何内容都不会被修改。
## 它如何判定
| 分组 | 含义 | 可以安全移除吗? |
|---|---|---|
| **CONFIRMED** | 符合结构规则和已知 payload | 是 |
| **HEURISTIC** | 在类似 shell 的结构上得分很高的未识别代码 | 请先阅读原因 |
| **REVIEW** | 真正的歧义 —— 既是真实单词也是工具包的文件名、被篡改的根 `.htaccess`、共享文件夹中的基线不匹配 | 绝不能自动处理 |
在任何模式下,REVIEW 项目都不会被自动隔离。这正是该分组的核心意义:该工具拒绝对您的网站进行盲目猜测。
在做出决定之前,您可以点击任何发现项以在浏览器中查看文件源码。预览仅限于当前扫描独立标记出的路径——它不是一个通用的文件读取 endpoint。
## 隔离
绝不直接删除任何内容。已确认的项目会被 **移动** 到扫描根目录下的 `_quarantine-/` 中,保留其原始目录结构,并附带以下内容:
- `QUARANTINE-REPORT.md` —— 移动了什么、来自哪里以及原因
- `manifest.tsv` —— 同样数据的电子表格就绪表格
- 一个拒绝所有访问的 `.htaccess` 和一个空的 `index.html`,因为该文件夹现在存放着活跃的恶意软件
将其作为证据下载,然后删除。重新运行时绝不扫描现有的隔离文件夹。
## 加固上传文件夹
**Harden**(加固)操作 (`--mode=harden`) 会在 `media`、`images`、`tmp`、`cache`、`administrator/cache`、`logs` 和 `files` 中写入一个 `.htaccess` 文件,阻止执行 `php php3-php8 phps pht phtml phar inc shtml shtm cgi pl` —— 特意设计为不区分大小写,因为它所防御的上传文件曾使用过 `.PHP`、`.pHp` 和 `.PHP5`。
如果已存在 `.htaccess`,将首先进行备份,并且已经被此脚本加固过的文件夹将被识别并跳过。
## 基线清单
清单是一个包含 **干净** 版本路径和哈希值的 JSON 文件。[`baselines/`](baselines) 中已有两个可供使用的清单:
| 文件 | 覆盖范围 | 文件数 |
|---|---|---:|
| [`baselines/joomla-6.1.2.json`](baselines/joomla-6.1.2.json) | Joomla 6.1.2 核心 | 9,660 |
| [`baselines/sppb-6.7.0.json`](baselines/sppb-6.7.0.json) | SP Page Builder 6.7.0 免费版 | 793 |
两者均是在干净的机器上从官方下载生成的,绝非来自运行中的在线网站。
将清单上传至扫描器旁边。它们会被自动识别——通过内容而非文件名,因此在传输到服务器时被重命名的清单依然有效。多个清单可以叠加使用,并且在您按下按钮之前,开始界面会告诉您哪些清单正在发挥作用。
对于任何其他版本或扩展,请在您的计算机上从全新下载的文件中构建您自己的清单:
```
php joomla-shell-scanner.php --mode=baseline --root=/path/to/clean/joomla-6.1.2 --out=joomla-6.1.2.json
php joomla-shell-scanner.php --mode=baseline --root=/path/to/clean/sppb-6.7.0 --out=sppb-6.7.0.json
```
切勿从您正在的网站构建基线——这会将入侵代码哈希到清单中,然后确认网站与之匹配。欢迎提交为其他版本添加清单的 Pull Request,只要它们来自官方下载即可。
## 同时扫描多个站点
开始界面有一个 **扫描文件夹** 输入框,带有向上遍历目录树的快捷方式。在共享主机上,您的所有站点通常都位于同一个父目录下:
```
/home/user/domains/
├── site-a.com/public_html/
├── site-b.com/public_html/
└── site-c.com/public_html/
```
将扫描器指向 `/home/user/domains`,这三个站点将在一次扫描中完成,并在报告中通过它们的完整路径进行区分。在任何入侵事件发生后,这都值得一做:如果一个入侵者访问了某个账户中的一个站点,通常就已经访问了该账户中的所有站点。
两点注意事项。数据库审查从扫描根目录读取 `configuration.php`,因此它仅在根目录是单个 Joomla 站点时才有效——需按站点运行该部分。并且,服务器拒绝读取的路径(由于 `open_basedir`,或者是指向账户之外的符号链接,例如 cPanel 在每个账户中放入的 `awstats/icon` 链接)将在报告顶部列为 *未扫描*,而不是被冒充为干净的。
## 命令行
```
php joomla-shell-scanner.php # scan, text output
php joomla-shell-scanner.php --mode=clean # quarantine confirmed items
php joomla-shell-scanner.php --mode=clean --include-suspect
php joomla-shell-scanner.php --mode=clean --include-heuristic
php joomla-shell-scanner.php --mode=harden # block PHP in upload folders
php joomla-shell-scanner.php --db # read-only database review
php joomla-shell-scanner.php --root=/path/to/site
php joomla-shell-scanner.php --baseline=a.json,b.json
php joomla-shell-scanner.php --mode=baseline --out=clean.json
php joomla-shell-scanner.php --fast # skip the heuristic pass
```
命令行绝不会要求输入 token,并且报告的检测结果与浏览器完全相同——一个检测数据取决于启动方式的扫描器是不值得信任的。
## 通过 cron 运行
某些主机完全阻止了对 `.php` 文件的直接请求,因此无论以何种方式上传扫描器,您都会收到 403 错误。cPanel 的 **Cron Jobs** 页面可以在不涉及 Web 服务器的情况下运行它:
```
/usr/local/bin/php /home/USER/domains/SITE/public_html/joomla-shell-scanner.php > /home/USER/domains/SITE/public_html/scan-report.txt 2>&1
```
将其设置为 `* * * * *`,等待一分钟,**删除该 cron job**,然后打开 `https://your-site.com/scan-report.txt`。如果 `/usr/local/bin/php` 不存在,请尝试 `/opt/cpanel/ea-php83/root/usr/bin/php`。
## 安全说明
**完成后将其删除。** 界面上有专门的按钮用于执行此操作。这是一个列出文件并移动文件的工具;它不是那种应该被遗留在生产服务器上的东西。
**默认情况下没有访问限制。** 该文件本意是在服务器上短暂留存(几分钟),而且在凌晨 3 点处理安全事件时,token 很容易成为另一个出错的环节。如果必须保留更长时间,请打开文件并设置:
```
define('CLEANUP_TOKEN', 'a-long-random-string');
```
此后,每个请求都需要附带 `?token=a-long-random-string`,并且输入错误的 token 会收到 404 而不是 403 响应——因为一扇锁着的门会告诉攻击者它背后藏着东西。
**扫描器自身不包含的内容:** 没有 `eval`,没有命令执行,没有任意的文件读取 endpoint。预览和移除操作仅限于当前扫描独立标记出的路径。源码是一个可读的文件;在运行它之前请先阅读它,这与您对待将要上传到被黑服务器上的任何其他东西时的态度应该是一样的。
它自身的特征列表中包含真实的 IOC 字符串,因此如果在同一个站点上存在第二个扫描器副本,它会将第一个报告为中文 webshell。它通过一个标记常量来识别自身,以避免这种情况。
## 清理之后
移除文件并不意味着入侵的结束。请大致按以下顺序进行操作:
1. **轮换所有凭据。** 这些 payload 中的一部分会读取整个服务器上的 `/home/*`。假设该主机上的每个数据库密码、Joomla 管理员密码、FTP/cPanel 登录信息以及 `.env` 都已泄露。
2. **检查文档根目录上方的 `.htaccess` 文件。** 它们会被继承,除非您启用显示隐藏文件,否则它们在文件管理器中是不可见的,并且它们是攻击者维持持久化驻留的绝佳位置。
3. **查看超级用户和最近创建的账户** —— 数据库审查会列出它们。
4. **更新 Joomla 和每一个扩展**,然后在所有超级用户上启用 MFA。
5. **执行 Harden(加固)操作。**
6. **一周后重新扫描。** 任何新文件的出现都意味着写入路径仍然是敞开的,而之前的文件清理仅仅是治标不治本。
## 局限性
- 它只是读取文件。它无法看到仅存在于数据库、cron job、受感染扩展的更新服务器或已经运行的进程中的 shell。
- 启发式引擎在处理高度压缩或生成的 PHP 代码时会产生误报。它会告诉您触发了哪些信号,以便您进行判断并忽略它们。
- 干净的扫描结果是证据,而非证明。基线清单是此工具中唯一接近完整性的部分,并且仅适用于您提供的版本。
- 它不是杀毒软件,也不自诩为杀毒软件。
## 环境要求
- PHP 7.2 或更高版本(已在最高至 8.5 的版本上测试)
- 适用于 Joomla 3、4、5 和 6 —— 也适用于非 Joomla 站点,在非 Joomla 站点上,位置和启发式规则仍然适用,而特定于 Joomla 的规则只是简单地什么也找不到
- 除了 PHP 核心之外不需要任何扩展;可选的数据库审查仅需要 PDO
## 许可证
GPL-3.0 —— 详见 [LICENSE](LICENSE)。
由 [Mohsen Khoshkam](https://moh3n.net) 携手 Claude Opus 5 编写。
如果它发现了什么有趣的东西,或者漏掉了什么本该捕获的东西,请带上它打印出的原因行提交一个 issue —— 这正是值得改进的地方。
标签:ffuf, Joomla, OpenVAS, PHP, Web安全, 后门查杀, 库, 应急响应, 文档结构分析, 蓝队分析