博主头像
mxd's Blog

以技术为翼,以生活为魂

PHP2Shell 这次,服务器也没能幸免

这几天 PHP 圈的氛围,用一个词形容就是:赛跑。

一边是 PHP 安全团队赶着发布补丁,另一边是拿到漏洞细节的人开始研究 PoC,还有人甚至不需要现成的 PoC,照着公开的信息就能自己动手。对维护服务器的人来说,最难受的事情莫过于此:你还在确认哪些服务受影响、升级会不会把现有环境搞坏,另一边已经有人开始扫描了。

出事了

然后,我的服务器也出事了。

你发现没有,包括我的本博客,就是 typecho,就是 php。

10 月 23 日凌晨,我在群里骂了一句「傻逼 PHP 漏洞」。然后我随手登上去一看,好几个挖矿病毒。更让人头大的是,数据库也被勒索了。

好在数据库有备份。

我折腾了几个小时,终于把服务恢复到能正常使用的状态。到凌晨四点多,已经顾不上再聊什么技术了,最后留下一句明天上课肯定睡成猪,然后睡觉。

事情暂时告一段落,但这次事故确实让我有点后怕。

PHP2Shell 到底是什么

这次受到关注的漏洞叫 PHP2Shell,对应 CVE-2026-109618 和 CVE-2026-109619。PHP 官方在 2026 年 10 月 21 日发布了相关安全更新,两个漏洞的 CVSS v4.0 评分均为 9.2,影响是未经身份验证的远程代码执行。https://www.php.net/archive/2026.php

它们的问题出在 PHP 解释器内部的内存管理和请求状态处理上,而不是某个 CMS 插件或者框架的业务代码。

其中一个漏洞与内存耗尽时请求数据尚未完成初始化有关,清理流程却可能把不完整的数据当作有效结构处理;另一个则涉及请求结束后残留的内部状态,后续请求可能使用已经失效的内存数据。具体触发条件和利用方式取决于相应的运行环境,不能简单地把所有 PHP 部署都视为完全相同。相关技术说明可以参考 Plesk 发布的 PHP2Shell 安全公告。

这类漏洞让人不舒服的地方,在于它不一定需要应用程序本身存在明显的安全问题。以 Apache mod_php 的相关利用路径为例,攻击可以发生在应用代码运行之前。你在 WordPress 里小心处理输入,在 Laravel 里加上中间件,甚至禁用了一批 PHP 函数,都不意味着能够绕过解释器本身的问题。

当然,这不代表应用层防护没有价值。只是当漏洞位于更底层时,上层做得再细致,也无法替代底层的安全修复。

补丁发布之后,事情才刚刚开始

PHP 官方在 10 月 20 日提前公布了即将发布重大安全更新的消息,并计划于 10 月 21 日 14:00 UTC 发布补丁。官方公告同时明确表示,没有完整的缓解措施,更新才是安全的解决办法。https://www.php.net/

这意味着维护者需要在很短的时间内完成几件事:确认实际运行的 PHP 版本,确认软件包来自哪里,检查发行版有没有回移补丁,再决定如何升级。

如果只是一个干净的测试环境,事情当然简单。拿到修复版本,替换运行环境,检查一下功能,服务就可以重新上线。

但真实环境没那么干净。

我的架构里还有 PHP 8.1。它已经在 2025 年 12 月 31 日结束官方常规安全支持,继续运行意味着需要依赖发行版、其他维护方提供的安全回移,或者自己维护补丁。PHP 安全团队这次也为部分旧版本提供了最小源码回移补丁,但具体能否直接使用,仍然取决于自己的构建方式和维护渠道。https://support.plesk.com/hc/en-us/articles/44397729587351-Vulnerability-CVE-2026-109618-and-CVE-2026-109619-PHP2Shell-Remote-code-execution-in-PHP-shipped-with-Plesk

我不太喜欢在生产环境里为了追版本而追版本。老项目有老项目的麻烦,某个扩展、某项配置或者某个历史遗留的组件,都可能让升级变成一次小型手术。

但这次的事情又让我意识到,长期停留在旧版本上省下来的兼容性调试时间,并没有真正消失。它只是被推迟了,直到某次安全更新突然变得非常紧急,才连本带利地还回来。

以后 PHP 的 minor 版本,我大概不会再跳着升了。更准确地说,我想把升级节奏安排得更规律一些,提前测试兼容性,尽量避免让生产环境长期停在已经结束支持的版本上。毕竟,临时抱佛脚时,连选择的余地都会少很多。

服务器被挂马之后,才知道恢复服务不等于恢复安全

这次最折腾我的,不只是漏洞本身,还有入侵之后的处理。

看到挖矿程序,第一反应肯定是先让服务恢复正常。但如果只把几个恶意进程杀掉,或者把 PHP 升级了,就认为事情已经解决,那还差得很远。

挖矿程序可能通过定时任务、启动项或其他方式重新运行。攻击者也可能留下后门、创建异常账号、修改网站文件,或者拿走服务器上的凭据。数据库被勒索,更意味着需要确认数据到底发生了什么变化,而不是看到网页重新打开就当作一切正常。

我现在能够确认的是,服务器出现了挖矿木马,数据库遭到了勒索,而且我有备份。至于攻击者究竟从哪个入口进来、是否直接利用了 PHP2Shell,以及入侵者还留下了什么,需要依靠日志和主机痕迹进一步确认。不能只因为这次事故发生在漏洞披露期间,就把攻击入口直接归因于这个漏洞。

如果确认主机已经被完全控制,比较稳妥的方案通常是从可信环境重建系统,再恢复经过检查的数据,并轮换可能泄露的数据库密码、API 密钥和其他凭据。恢复服务只是其中一步,确认原来的入侵路径已经关闭、持久化机制已经清除,才算真正接近收尾。

这也是为什么我不太敢把这次经历简单地称作「修好了」。至少在安全事件里,能访问网站和能够信任服务器,是两个不同的概念。

最后还是备份

这次让我庆幸的事情,就是数据库有备份。

平时谈备份,总觉得是个老话题。数据库定时导出,配置文件单独保存,重要数据再放到其他机器上,似乎没有什么新鲜的。

但当数据库真的被勒索时,这些事情就不再只是配置清单上的几行字了。备份意味着还有机会把数据恢复回来,不必完全依赖攻击者,也不必从零开始重建整个站点。

前提是备份真的能用。

如果备份一直放在被入侵的服务器上,攻击者可能连备份一起删掉;如果只有最新的一份,恶意修改可能早已进入备份;如果从来没有测试过恢复流程,等到真正需要的时候,也可能发现文件不完整,或者根本无法恢复。

所以比起单纯确认「我有备份」,更重要的是确认备份是否独立、是否保留历史版本,以及能不能在一台已经不可信的机器之外完成恢复。

这次我至少没有在数据库这里彻底翻车。服务器被折腾成这样,能有一份可用的备份,已经算是不幸中的万幸了。

PHP2Shell 这类漏洞迟早会被下一波安全事件盖过去,但服务器维护不会因此变得轻松。运行时、Web 服务、依赖和应用之间的关系越复杂,越难保证每个环节都能及时响应突发的安全问题。

而我能做的,也就是把该做的更新安排好,把备份留在可靠的地方,再尽量避免让自己在下一次漏洞披露时,还得熬到凌晨四点抢救服务器。

把 PHP 升级到修复版本,可以堵住对应的漏洞,却不能保证攻击者留下的后门已经消失。如果已经确认主机被完全控制,重要凭据就应该视为可能泄露,必要时从可信环境重建系统,再轮换数据库密码、API 密钥和其他凭据。对于运行了很久、自己也不完全清楚被改过哪些东西的服务器,重建有时比试图把每一处恶意修改都找出来更可靠。

说起来容易,做起来就完全是另一回事了。尤其是有多个站点、多个服务,还有一堆历史配置的机器,重建意味着要重新确认依赖关系、恢复数据、验证服务,哪一步出错都可能引出新的问题。

所以我也不想把这次经历包装成什么安全实践心得。说到底,就是半夜发现服务器被人搞了,顶着一堆破事抢救了几个小时,最后好歹让网站重新跑起来了。

凌晨四点,终于可以睡觉。

至于明天的课,大概是不用指望认真听了。折腾到这个点,估计往教室一坐就能睡成猪。

服务器恢复了,人还没恢复。

PHP2Shell 这次,服务器也没能幸免
https://blog.mxdyeah.com/post/php2shell-server-incident
本文作者 mxdyeah
发布时间 2026-10-23
许可协议 CC BY-NC-SA 4.0
发表新评论