今晚打算写点别的,结果先干了一场仗——我的博客被黑了,而且已经悄悄烂了九天。
起因很简单:有朋友说访问博客会跳到一个印尼新闻网站。我第一反应是 frp 内网穿透出问题了,毕竟博客跑在我自己电脑的 Docker 里,外面套了层 frp。查了一圈,frp 完全无辜——它老老实实把流量转给了本地的 WordPress,跳转是 WordPress 自己吐出来的。
curl 跟了一遍跳转链:博客域名先 301 到 www,再 302 到 news.detik.com。破绽在响应头里:HTTP 头写着 nginx/1.24.0,302 页面正文却署名 Apache/2.4.66 (Debian)——说明请求前面是 nginx(云服务器),后面还有个 Apache(我的 WordPress 容器)在搞鬼。本机直接 curl 127.0.0.1:8080,同样的 302,一模一样。抓到了。
真凶是 .htaccess 里被注入的一段规则,名字还挺有礼貌,叫 “ScrapingVisitor”:
它的逻辑是看人下菜碟:搜索引擎爬虫放行,让 Google 和百度收录垃圾页;手机访客被甩到一个短链接(指向博彩站);其他所有真人统一被踢去印尼新闻站。我手机访问看到的是博彩页,curl 访问看到的是印尼新闻——这叫 cloaking,黑帽 SEO 的经典手艺。
更膈应人的是它在规则里特意排除了两个文件:bootstrap.inc.php 和 bootstrap.mcp.php。排除这两个路径意味着无论网站跳成什么样,攻击者自己的后门永远畅通——相当于把主人锁在门外,自己留了把钥匙。
顺着时间戳往下挖,入侵从 10 月 1 号就开始了(trace.php),10 月 4 号晚上完成总攻:一口气铺了 50 个印尼语博彩垃圾页,路径完全模仿我的文章链接格式(/2020/10/21/文章名/),还伪造了 robots.txt、sitemap.xml,甚至还有一份专门投毒大模型爬虫的 llms.txt。因为静态文件优先于 WordPress 路由,这些垃圾页直接把我原来的文章顶掉了,数据库却毫发无伤——不仔细看根本发现不了。
清理倒不复杂,讲究一个稳字:
- 先全量备份(18781 个文件 + 64MB 数据库),留好后悔药
- 停容器,把 10 个木马 PHP、9 个泛目录、5 个伪造 SEO 文件移动到隔离目录——不删,先隔离,既能回滚又能取证
- 重写 .htaccess,删掉恶意段,顺手加了防护规则
- 重置 wp-config.php 里全部 8 个安全密钥,攻击者留的登录态全部作废
- 重启,外网全链路验证:所有 UA 都是 200,原文章全部恢复
有两点教训值得记下:
一、个人博客的公网暴露面就是攻击面。frp 把 wp-login 和 xmlrpc 直接怼到公网上,配合一堆没更新的插件,等于门没锁还把钥匙插在锁上。后面打算给后台加 IP 白名单、禁用 xmlrpc、插件全部更新一遍。
二、出问题先看证据,别急着怀疑链路。这次差点先去折腾 frp 配置,幸好先 curl 了一下本地容器——同样的 302 在本机就复现了,frp 三秒排除嫌疑。诊断的顺序应该是:先抓包看跳转链 → 再定位每一跳是谁发的 → 最后才动配置。
凌晨十二点半,网站恢复如初。这篇日记本身就是发在被修复的博客上的,算是个小小的纪念——以后每次写日记,服务器都记得今天晚上它曾经被谁闯进来过。
P.S. 如果你也在用 frp 内网穿透跑 WordPress:检查一下你的 .htaccess,搜 “ScrapingVisitor”,说不定有惊喜。
