ThinkPHP缓存键本身不直接导致目录穿越,但若参与文件路径构造且未经realpath()规范化及根目录白名单校验,如$key=../../config/database,经拼接后可写入任意路径;防范核心是杜绝用户输入直拼路径,强制标准化并锁定根目录。

ThinkPHP缓存键本身不直接导致目录穿越,但当缓存键参与文件路径构造(尤其是File驱动)且未经规范化与白名单校验时,就可能被用于写入任意路径——比如把../../../public/shell.php作为缓存键,最终生成恶意文件路径。防范核心不是“过滤键名字符”,而是切断“用户输入→可写路径”的映射链条。
缓存键不能直接拼进文件路径
TP默认的File缓存驱动会将缓存键(如user:123)经md5()哈希后生成文件名(如abc123.php),再拼到cache_path下。这是安全的。但一旦开发者手动用input('key')拼接路径,例如:
$file = CACHE_PATH . input('key') . '.php'; file_put_contents($file, $data);- 或自定义缓存类中调用
file_put_contents($this->root . '/' . $key, $val)
这就绕过了框架默认防护,使key=../../config/database直接触发目录穿越。必须杜绝此类写法。
强制路径标准化 + 根目录锁定
若业务确需动态缓存路径(如多租户隔离),对用户传入的缓存标识做以下处理:
- 先用
realpath()解析完整路径,再用str_starts_with()确认其落在预设安全根目录内(如CACHE_PATH) - 示例:if (!str_starts_with(realpath($userPath), realpath(CACHE_PATH))) { throw new InvalidArgumentException('非法缓存路径'); }
- 禁用
..、.、%2e等所有跳转变体——不靠字符串替换,而靠realpath()解码+标准化后再比对
禁用危险缓存键内容
在Cache::set()入口或自定义缓存封装层增加白名单校验:
- 只允许字母、数字、下划线、短横线、冒号(
[a-zA-Z0-9_\-:]),其余一律拒绝或替换为空 - 拒绝含
.htaccess、.user.ini、.php等敏感后缀的键名(即使不拼路径,也防误配置) - 对
TagCache等支持反序列化的缓存类型,额外限制键名长度(如≤64字符),避免超长键名引发解析异常或绕过
Web服务器层兜底防护
即使代码层出疏漏,也要让攻击者无法执行写入的文件:
- Nginx配置中对
cache/目录禁用PHP解析:location ~ ^/runtime/cache/.*\.php$ { deny all; } - 确保
cache_path不在public/下,且该目录无Web访问权限(如设为/var/www/myapp/runtime/cache/,Web root仅指向public/) - 禁止上传或写入
.htaccess、.user.ini——这两类文件可覆盖MIME解析规则,让图片变成PHP执行
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











