ThinkPHP vendor路径被用户控制导致文件包含的关键在于业务代码是否直接将未过滤的用户输入(如$_GET['file'])用于include/require等函数,而非框架自身漏洞;需严格校验路径合法性并禁用debug模式。

ThinkPHP vendor 路径被用户控制导致文件包含?先看是否真能绕过
ThinkPHP 5.1+ 默认禁用 vendor 目录下的类自动加载外部路径,但若业务代码手动拼接 include 或 require,且未过滤用户输入的文件名参数,就可能触发路径遍历。关键不是框架“有没有漏洞”,而是你写的那行 include $filename 是否把 $_GET['file'] 直接喂进去了。
常见错误现象:Warning: include(../runtime/log/202405.log): failed to open stream 这类报错一旦出现,说明路径已失控;更隐蔽的是返回空白但日志里有异常包含记录。
- 检查所有使用
include、require、file_get_contents、file的地方,确认变量是否来自$_GET、$_POST或路由参数 - ThinkPHP 的
view渲染默认不允许多级目录访问(如{include file="../../config/database.php"}会被截断),但自定义模板解析逻辑不受此限 - 别信“我只用了
think\facade\View就安全”——只要底层调了file_exists+include且参数可控,风险就存在
basename() 和 pathinfo() 不是万能解药
很多人加一层 basename($_GET['file']) 就以为万事大吉,但攻击者用 %2e%2e%2f(URL 编码的 ../)或双写 ....// 可绕过。ThinkPHP 自身在部分版本中对 URL 解码时机处理不当,basename() 处理的是解码后字符串,而中间件或路由可能已提前解码并拼接。
真正有效的做法是:在路径拼接前,用白名单限定允许的文件后缀和基础目录,并强制规范路径。
立即学习“PHP免费学习笔记(深入)”;
- 用
realpath()获取绝对路径后,判断是否仍落在预期目录内,例如:strpos(realpath($user_path), ROOT_PATH . 'public/static/') === 0 - 禁止任何
..、/./、//出现在原始输入中,用正则/(\.\.\/|\/\.\.\/|\/\/|\/\.\.)/提前拦截 - ThinkPHP 6 的
think\File类自带isDenyExt()和checkPath(),但仅用于上传校验,不能直接套用到包含逻辑里
ThinkPHP 路由参数里的路径解析陷阱
当路由定义为 route('download/<filename>', 'index/download')</filename>,且控制器里直接 file_get_contents('public/files/' . $filename),这就是典型高危场景。ThinkPHP 会自动对 $filename 做 URL 解码,但不会做路径规范化——filename=..%2f..%2fconfig%2fdatabase.php 会被解成 ../../config/database.php。
这个问题在 ThinkPHP 5.0.x 和 5.1.0–5.1.37 中尤其明显,修复版增加了 Request::filterValue(),但默认不启用路径过滤。
- 不要依赖路由参数“看起来像文件名”就放行,一律视作不可信输入
- 若必须传文件名,改用哈希 ID 映射(如
download/abc123→ 查数据库得真实路径),彻底切断用户与路径的映射关系 - ThinkPHP 6.1+ 支持在路由绑定中加验证规则,例如
->pattern(['filename' => '\w+\.(txt|pdf|jpg)']),但注意这只是正则匹配,不防编码绕过
配置项 app_debug 开启时的额外风险
当 app_debug = true,ThinkPHP 会在错误页面中暴露完整路径(如 /var/www/html/app/../runtime/),这等于把服务器结构图送给攻击者。配合任意一个路径拼接点,就能精准构造遍历 payload。
更麻烦的是,调试模式下某些异常堆栈会打印出 include 的完整调用链,包括变量值——哪怕你用了 basename(),它也可能显示解码前的原始值。
- 生产环境必须关掉
app_debug,且确保display_errors = Off和log_errors = On在 PHP 配置中生效 - 检查
config/app.php中的error_handler和exception_handle是否被自定义覆盖,避免意外泄露路径 - 即使关了 debug,如果用了第三方日志组件(如 Monolog)且配置了
stream输出到runtime/,也要确认其FileHandler没有把用户输入写进日志路径
路径遍历最棘手的地方不在“怎么拦”,而在“哪里漏”。ThinkPHP 的路由、中间件、模板、日志、缓存模块各自有一套路径处理逻辑,它们之间没有统一的过滤入口。盯着一个 include 补丁,不如翻一遍 grep -r "include\|require\|file_get_contents" app/,再查查 vendor 里有没有你引入的包偷偷做了路径拼接。



















