thinkphp 6.0.13及更早版本在多语言开启且lang参数可控时存在真实lfi漏洞;需检查config/lang.php开关、框架版本≤6.0.13、lang参数是否被lang::detect()直接使用。

ThinkPHP 6.0.13 及更早版本中,只要多语言功能被开启且未做路径校验,lang 参数就可被用于本地文件包含(LFI),这是真实可利用的漏洞,不是理论风险。
如何快速确认项目是否受影响
别猜,直接查三处关键点:
-
config/lang.php中'switch' => true或'lang_switch_on' => true—— 表示多语言已启用 - 执行
php -r "echo \think\facade\App::version();",输出 ≤6.0.13即在风险版本内 - 检查请求中是否接收并使用了
lang、think-lang或自定义的lang_detect_var参数(如通过$_GET['lang']直接传给Lang::detect())
只要同时满足「多语言开启 + 版本 ≤ 6.0.13 + lang 参数可控」,就具备基础利用条件。注意:即使没装 PEAR,也能读取任意 PHP 文件源码(如 index.php),只是无法直接 GetShell。
检测时最常踩的三个坑
很多团队扫了日志或跑了几条 Payload 就说“没漏洞”,其实漏掉了关键细节:
- 误以为只有
GET请求才触发 —— 实际上POSTbody、HTTP think-langheader、甚至Cookie都可能被框架捕获,取决于lang_detect_var配置 - 用
../../../../etc/passwd测试返回 404 就放弃 —— LFI 在 ThinkPHP 中只支持加载.php后缀文件,/etc/passwd不是 PHP 文件,自然不报错也不回显;应改用../../public/index.php看是否解析出 PHP 代码或报错 - 忽略环境依赖盲目复现 PEAR 利用链 ——
register_argc_argv=On和/usr/local/lib/php/pearcmd.php存在缺一不可,但这两项在 Docker 容器或云主机上默认关闭/不存在,导致 payload 无响应,不代表漏洞不存在
修复必须改代码,光关配置不够
仅把 config/lang.php 的 switch 设为 false 是治标。真正安全的做法是:
- 升级到
6.0.14+或6.1.0+—— 官方已在Lang::detect()中加入basename()和白名单校验 - 若无法升级,手动加固
think\lang.php:在detect()方法中对$lang值强制截取后缀并限定范围,例如:$lang = basename($lang); if (!in_array($lang, ['zh-cn', 'en-us'])) { $lang = 'zh-cn'; } - 禁用危险 PHP 配置(需服务器权限):
allow_url_include = Off、register_argc_argv = Off,并删除或重命名pearcmd.php
别信“加个 Nginx 规则就能防住”的说法 —— ThinkPHP 的 lang 解析发生在 PHP 层,Nginx 日志规则只能辅助审计,不能拦截实际包含行为。
自动化检测工具要慎用
像 thinkphp_gui_tools 这类 Java 工具能批量探测,但存在明显局限:
- 默认 payload 假设目标装了 PEAR 且
register_argc_argv=On,在多数现代环境里会误报“不存在” - 图形界面掩盖了真实请求细节,比如没显示 header 中的
think-lang是否生效,容易漏掉非 GET 场景 - 部分模块基于 TP5 的
App::run()行为,对 TP6 的Http::run()兼容性差,返回 500 不代表漏洞存在,可能是路由层异常
最稳的方式还是人工构造请求 + 查看响应体是否含 PHP 解析结果(如 <?php 被执行、Parse error 报错、或意外输出了其他 PHP 文件内容)—— 这比任何工具都直接。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











