起因

PHP 网站(WordPress、Discuz 这类)最常见的失守路径:上传漏洞或注入被利用 → 攻击者往网站目录写进一个 webshell(一句话木马)→ 之后为所欲为。webshell 的本质就是一段 PHP 代码,它能做的事完全取决于 PHP 环境还开放哪些函数。所以除了修漏洞,还要做一层纵深防御:把危险函数禁掉,就算 webshell 进来了也只剩残废的手脚。

需求

  • 修改 php.ini 的 disable_functions,禁用命令执行、进程控制、网络连接、信息搜集类危险函数
  • 不影响网站正常业务

技术实现原理

这份清单为什么禁这些函数(按攻击链条分组)

webshell 拿到手后,攻击者的下一步动作对应的正是这几类函数:

  1. 命令执行类:exec、system、passthru、shell_exec、popen、proc_open、pcntl_exec——直接拉起 shell 跑系统命令,是 webshell 最想要的能力。禁掉后攻击者只能用 PHP 语言本身,能力大打折扣;
  2. 进程控制类:proc_close、proc_terminate、proc_nice、posix_kill——管理/干扰系统进程;
  3. 网络类:socket_* 系列、stream_socket_client、stream_socket_server、pfsockopen——两个用途:反弹 shell(socket_connect 回连攻击者机器,绕过防火墙的方向限制)和内网横向扫描。禁掉后 webshell 只能被动等 HTTP 请求进来;
  4. 信息搜集类:getrusage、get_current_user、getmyuid、getmypid、disk_free_space、posix_getpwuid、posix_getgrgid——摸清系统用户、磁盘、PID,为提权做准备;
  5. 文件系统/环境边界类:symlink、link、chgrp、mkfifo、dl、putenv、ini_alter、ini_restore——软链接攻击(symlink 指向敏感文件实现越权读取)、加载恶意扩展(dl)、篡改环境变量(putenv 配 LD_PRELOAD 是很多绕过手段的起点)。

一句话总结原理:disable_functions 不是修漏洞,而是砍掉 webshell 的手脚。上传/注入漏洞照样要修,这里是假设"第一道防线已破"时的第二道防线。

配置怎么生效(“改了不生效"的根因)

  • PHP 是启动时读一遍 php.ini,把 disable_functions 里的名字装进禁用表;之后调用这些函数直接报 has been disabled for security reasons 的致命错误;
  • 所以改完必须重启 php-fpm 才生效——“改了配置没反应"的根因几乎都是没重启,或者改的不是实际加载的那个 ini;
  • 确认实际加载的配置:网页端看 phpinfo() 的 Loaded Configuration File,命令行用 php --ini。注意 CLI 和 php-fpm 常常是两个不同的 ini。

找配置文件

源码编译安装的 PHP,配置在各自的 etc 目录;包管理器(yum/apt)安装的在 /etc 下。

PHP5.6版本位置:

1
/usr/local/php56/etc/php.ini

PHP7.3版本位置

1
/usr/local/php73/etc/php.ini

或者:

1
    /etc/php.ini

php.ini文件disable_functions配置如下:

1
    disable_functions=exec,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,proc_open,ini_alter,dl,popen,pcntl_exec,socket_accept,socket_bind,socket_clear_error,socket_close,socket_connect,socket_create_listen,socket_create_pair,socket_create,socket_get_option,socket_getpeername,socket_getsockname,socket_last_error,socket_listen,socket_read,socket_recv,socket_recvfrom,socket_select,socket_send,socket_sendto,socket_set_block,socket_set_nonblock,socket_set_option,socket_shutdown,socket_strerror,socket_write,stream_socket_client,stream_socket_server,pfsockopen,disk_total_space,disk_free_space,diskfreespace,getrusage,get_current_user,getmyuid,getmypid,dl,leak,listen,chgrp,link,symlink,dlopen,proc_nice,proc_get_stats,proc_terminate,shell_exec,sh2_exec,posix_getpwuid,posix_getgrgid,posix_kill,ini_restore,mkfifo,dbmopen,dbase_open,filepro,filepro_rowcount,posix_mkfifo,putenv

踩坑记录与注意事项

  • 改了不生效:九成是没重启 php-fpm(systemctl restart php-fpm/etc/init.d/php-fpm restart),其余是改错了 ini——先 php –ini / phpinfo() 确认 Loaded Configuration File;
  • 一次禁太多业务会崩:putenv、proc_open 是 Composer、队列等工具的常见依赖。稳妥做法是分批:先禁命令执行类,业务跑几天没问题,再加网络类和文件边界类;
  • escapeshellarg / escapeshellcmd 在清单里要知情:这两个本身是"给 shell 命令做转义防注入"的防御函数,禁掉后依赖转义的代码会直接报错,属于激进配置,按业务取舍;
  • disable_functions 不是完整沙箱:mail() 调 sendmail、ImageMagick 等通道仍在。要求更高的场景叠加 open_basedir 限制可访问目录 + php-fpm 用独立低权限用户运行;
  • 验证方法:放一个 <?php var_dump(exec('id')); ?>,访问报 disabled 说明已生效(验证完立刻删除)。