必须移除unserialize()调用、关闭APP_DEBUG、在disable_functions中显式禁用exec/system/eval等函数,否则其他加固形同虚设;三者是ThinkPHP安全基线的刚性要求,缺一不可。

unserialize() 调用必须移除,APP_DEBUG 必须关死,disable_functions 必须包含 exec、system、eval 等函数——这三件事不做,其他加固都是补丁贴在漏气的轮胎上。
为什么 ThinkPHP 安全加固总在“打补丁”上翻车
很多团队一听说 ThinkPHP 有漏洞,第一反应是搜“ThinkPHP 5.1.40 补丁”,然后改个版本号就以为万事大吉。但真实风险往往不在框架版本本身,而在你写的那几行 unserialize($_GET['data'])、那个忘了关的 APP_DEBUG=true、或者 php.ini 里还开着的 allow_url_include=On。这些配置和代码层的疏漏,让攻击者根本不需要利用 CVE,就能直捣后端。
必须干掉的三个反模式调用点
ThinkPHP 应用中真正高频、高危、且极易被忽略的入口,就这三个:
-
unserialize():检查所有控制器、中间件、缓存类,尤其注意input('data')或$_POST['payload']后直接跟unserialize(base64_decode(...))的写法;一律替换成json_decode(),不接受“历史原因” -
Db::query()拼接 SQL:哪怕只有一处Db::query("SELECT * FROM user WHERE id = ".$_GET['id']),整个数据库就等于裸奔;强制改用where('id', $id)或参数绑定形式 -
think\facade\View::fetch()动态模板名:如View::fetch(input('tpl')),攻击者可传入../runtime/log/202610/xxx.php触发任意文件读取;模板路径必须白名单硬编码或严格校验前缀
php.ini 和 Nginx 层不能妥协的五项配置
框架层再小心,也挡不住底层 PHP 配置放行危险行为。以下五项必须逐条核对,不能靠“默认值”蒙混:
立即学习“PHP免费学习笔记(深入)”;
-
disable_functions中必须显式列出:exec,system,shell_exec,passthru,proc_open,popen,eval,assert,create_function -
display_errors = Off且log_errors = On;error_log路径需设为非 Web 可访问目录(如/var/log/php-app-error.log) -
open_basedir必须设为项目根目录 + 临时目录,例如:/www/wwwroot/myapp/:/tmp/:/var/tmp/;不要留空或设为/ -
allow_url_fopen = Off且allow_url_include = Off;若业务真依赖远程资源,改用 cURL + 白名单域名校验 - Nginx 配置中,对
public/runtime/、public/log/等目录加deny all,并禁止匹配.php$的上传路径执行(如location ~* /uploads/.*\.php$ { deny all; })
最易被绕过的“安全开关”:APP_DEBUG 和路由暴露
很多人以为把 APP_DEBUG 改成 false 就安全了,但实际常踩两个坑:
- 环境变量覆盖:.env 文件里写了
APP_DEBUG=false,但服务器上又通过putenv('APP_DEBUG=true')或 Apache 的SetEnv覆盖了它;务必在代码里加var_dump(config('app.app_debug'))实测 - 路由路径明文化:后台仍用
/admin,扫描器 3 秒就能扫出;应彻底弃用模块名硬编码,改用动态路由前缀(如从配置读取config('admin.prefix')),并在 Nginx 伪静态中同步适配
复杂点在于,这些配置不是孤立生效的——比如 open_basedir 设错会导致日志写入失败,进而掩盖真正的错误;disable_functions 漏掉 proc_open,就可能让某些日志组件悄悄绕过限制。每一条都得验证,而不是“改完就上线”。



















