上个月帮朋友排查一台跑开源 CMS 的服务器,发现由于第三方插件上传漏洞,网站附件目录被上传了一个经过字符混淆的一句话木马。虽然外层的 Nginx 防火墙能挡住绝大多数已知扫描,但一旦攻击者用自定义编码或加密通信绕过流量检测,后门脚本一旦落盘并被 PHP-FPM 成功解析,就很容易拿到系统权限。
顺手把这次在宝塔面板环境下的加固过程记录下来,主要从 PHP 运行时的底层做限制,哪怕木马侥幸被传上去了,也让它无法执行系统命令、无法跨目录漫游。
1. 为什么流量层防御容易被木马绕过
很多人以为前面套了 Nginx 或者云厂商的 WAF 就高枕无忧了,但实际攻防里,WebShell 的变形手段太多了:利用反射调用、字符串拼接、异或运算、甚至直接通过 openssl_decrypt 解密后丢给 create_function 或 assert 执行。流量检测往往很难百分之百识别这类合法且加密的数据包。
真正把后门扼杀掉的关键节点,是在 PHP 脚本被解释执行的那个瞬间。只要在 PHP 核心底层断掉它的执行路径,比如剥夺调用系统 Shell 的权利、锁死当前网站的访问根目录,木马就算被访问,也只能报错退出,造不成实际破坏。
2. 基础加固:完善 disable_functions 与防跨站
加固的第一步永远是把不需要的系统级函数关掉。宝塔默认安装的 PHP 已经禁用了一部分,但对于生产环境来说,依然偏向保守。编辑对应 PHP 版本的 php.ini,重点核对并补充以下函数:
| 函数名称 | 风险类型 | 禁用原因与说明 |
|---|---|---|
exec, shell_exec, system, passthru | 命令执行 | 绝大多数 Web 程序不需要直接调用终端命令,木马提权核心入口 |
proc_open, popen, proc_get_status | 进程操作 | 常被用来绕过普通命令禁用函数,反弹 Shell 必备 |
putenv, getenv | 环境变量篡改 | 常用于配合 LD_PRELOAD 机制突破 disable_functions 限制 |
ini_alter, ini_restore, dl | 运行时改写 | 防止黑客在执行过程中动态恢复配置或加载外部恶意外挂模块 |
除了禁用函数,必须在宝塔站点的“网站目录”中勾选“防跨站攻击(open_basedir)”。这个设置会生成一个 .user.ini 文件,强制把当前 PHP 进程的文件读取范围限制在当前站点的根目录和系统的 /tmp 目录中,防止某一个站点被攻破后,通过 ../../ 遍历读取其他站点的源码或数据库配置文件。
3. 启用宝塔 PHP 安全防护模块
对于需要防御变形木马的场景,单靠 disable_functions 往往防不住内存马或内部代码注入。可以在宝塔面板的 PHP 设置页面中,找到自带的“PHP安全防护”扩展。
这个功能本质上是一个基于 Zend 引擎内核的扩展,挂载在 zend_execute 的生命周期上。相比于外部的文件扫描,它工作在运行时:
- 危险调用拦截:当检测到代码尝试调用被禁用的敏感底层接口,或者通过动态可变变量构造命令时,直接在内核层阻断并终止请求。
- 一句话木马拦截:检测
assert、eval、preg_replace /e模式下的恶意载荷,即便做了复杂的编码混淆,只要在运行时解密出恶意代码,就会被底层探针捕获。 - 防提权限制:严格拦截对敏感系统路径(如
/etc/passwd、/proc/version等)的异常读取行为。
在“软件商店”找到当前使用的 PHP 版本,进入“扩展配置”或安全防护功能页开启即可。如果系统使用了多个 PHP 版本,建议每个版本的防护都独立开启并保存配置,之后重启对应的 PHP-FPM 服务让扩展生效。
4. 常见误报排查与平稳运行
开启严格的底层防护后,偶发出现误报是正常现象,通常发生在后台导出 Excel、调用 FFmpeg 处理视频、或者使用 Composer 的场景。遇到功能受阻时,可以按照以下步骤处理:
- 查看日志定位具体行为:在宝塔面板的“安全”或 PHP 管理的日志选项卡中,查看拦截记录,确认是哪个文件(比如某个导数脚本)触发了哪条拦截规则(通常是触发了
proc_open或特定正则)。 - 针对性放行而非全盘关闭:如果某个业务确实必须调用外部命令,不建议直接关闭全站防护,可以在模块的白名单配置中,将该合法脚本的绝对路径加入排除名单。
- 检查 Web 目录权限:确保 Nginx 运行用户(通常是
www)没有系统敏感目录的写入权限,上传目录(如/uploads/)在 Nginx 层面配置location ~* \.(php|php5|phtml)$ { deny all; },彻底禁止执行任何 PHP 文件,与底层安全模块形成双重隔离。