thinkphp缓存漏洞是多版本共有的系统性风险,核心在于缓存内容可被恶意构造、直接解析且缺乏校验;需分层阻断利用链,包括禁用文件缓存、加固序列化处理、过滤输入及nginx层防护。

ThinkPHP缓存漏洞不是单一问题,而是多个版本中反复出现的系统性风险:从3.2.3的缓存写入未过滤、到5.0.x将序列化数据直接存为.php文件、再到高并发下缓存文件损坏引发反序列化报错——它们共同指向一个核心问题:缓存内容可被恶意构造、可被服务器直接解析、且缺乏运行时校验。修复不能只靠“删runtime”,必须分层阻断利用链。
缓存文件被写入WebShell(典型RCE)
攻击者通过可控参数(如 s=/Index/\think\app/invokefunction 或缓存键名)触发框架将恶意PHP代码序列化后写入 runtime/cache/xxx.php。该文件路径可预测,若Web服务器允许直接访问.php后缀,或结合任意文件包含漏洞,即可执行任意代码。
- ThinkPHP 5.0.0–5.0.10:缓存驱动默认将
serialize($data)结果写入.php文件,且未做任何内容白名单校验 - ThinkPHP 3.2.3:使用
S('key', $_POST['a3'])时,若未过滤换行符,攻击者可注入<?php eval($_POST[1]);?>并通过%0A换行绕过 - 关键特征:日志中出现
index.php?..eval、cache/目录下出现非预期的 .php 文件、Nginx 访问日志中大量带s=参数的请求
缓存反序列化失败导致服务异常
不是所有缓存问题都用于攻击,但损坏的缓存文件会直接引发 unserialize(): Error at offset,造成接口500、页面空白等故障。根本原因在于缓存写入过程不原子:
- 服务器异常断电或强制重启时,
file_put_contents()可能只写入部分序列化字符串 - 多个进程同时写同一缓存键,无文件锁机制,导致内容错乱
- 磁盘满、IO错误、UTF-8与GBK混用导致字符串长度记录与实际字节不一致
- 表现:
runtime/cache/下出现大小为0、4、8字节的空文件,或内容明显截断(如开头是a:1:{s:4:后戛然而止)
真正有效的修复动作(不依赖停机)
修复重点不是“能不能停机”,而是“是否引入新风险”。以下操作均可在线完成,无需中断用户请求:
-
立即清理并禁用危险缓存路径:删除
runtime/cache/全部内容;在config/cache.php中将缓存驱动改为redis或memcached,彻底规避文件写入风险 -
强制路由 + 关闭调试模式:设置
'url_route_must' => true和'app_debug' => false,让所有非法s=参数请求直接404,不进入路由解析环节 -
补丁级加固(不升级框架):
- TP3.2.3:修改
thinkphp/library/think/cache/driver/File.php的set()方法,在序列化前过滤换行:$value = str_replace(["\r", "\n"], '', $value); - TP5.x:重写
think\cache\driver\File::tagSet(),对缓存值做base64_encode(serialize($value)),读取时先base64_decode再unserialize,避免原始PHP代码落入文件
- TP3.2.3:修改
-
配置与缓存双清:执行
php think clear --all清除全部缓存;若用过php think optimize:config,必须手动删掉runtime/cache/下所有.php配置缓存文件
长期防御建议
临时打补丁治标,架构调整才能治本:
- 生产环境禁用
file缓存驱动,改用 Redis 并设置密码和访问白名单 - 所有用户输入进缓存前,强制类型转换(如
(string)$input)并限制长度,杜绝不可信数据落盘 - 在 Nginx 层添加规则拦截高频
s=请求、含invokefunction/call_user_func的URL,返回403 - 定期扫描
runtime/cache/目录,用file -i检查文件类型,自动告警非文本类 .php 文件
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











