lang参数能绕过basename()是因为其仅截取路径末段,对../../../../etc/passwd等纯相对路径无效,仍会拼接成lang/../../../../etc/passwd.php;真正有效防御是先realpath()解析再比对白名单目录。

lang 参数为什么能绕过 basename() 直接穿透目录
因为 basename() 只截取最后路径段,对 ../../../../etc/passwd 这种纯相对路径完全无效——它返回的仍是 passwd,而真实加载时框架会拼成 lang/../../../../etc/passwd.php,最终等价于 /etc/passwd.php。更危险的是,攻击者还能用 URL 编码(%2e%2e%2f)、双斜杠(.//)、空字节(%00)等手法绕过简单字符串替换或正则过滤。
真正起作用的只有两步:先用 realpath() 解析出绝对路径,再比对是否落在合法语言包根目录下。其他任何“删点删斜杠”操作都是假防御。
如何在中间件里安全校验 lang 参数
不要依赖控制器或验证器里的 lang() 调用去拦截——旧版中 lang() 内部仍会触发不安全的 Lang::load(),此时校验已晚于文件加载。
必须在请求进入业务逻辑前就掐断恶意值,推荐在自定义中间件中处理:
- 读取
$request->param('lang', 'zh-cn'),注意不能用input('lang'),它可能被污染 - 拼出目标文件路径:
$file = config('lang.path') . DIRECTORY_SEPARATOR . $lang . '.php' - 用
realpath($file)获取真实路径,若返回false立即拒绝 - 用
str_starts_with(realpath($file), realpath(config('lang.path')))判断是否在白名单目录内 - 校验失败时抛出
HttpException(400),不要只 redirect 或静默忽略
升级到 6.0.14 后还要改配置吗
要。升级只是修复了 Lang::load() 内部的路径拼接逻辑,但以下配置项仍可能让漏洞复活:
-
config/lang.php中的'switch' => true必须设为false,否则中间件仍会解析lang参数 -
app/middleware.php中若注册了\think\middleware\LoadLangPack::class,且项目实际不需要多语言,直接删掉这行 - 确认
APP_DEBUG为false,否则错误页面可能泄露realpath()失败的原始路径信息 - 禁用
register_argc_argv(PHP 配置),防止通过命令行参数绕过 Web 层校验
验证器里调用 lang() 的隐藏风险
很多人在自定义规则提示里写 lang('validate.required'),以为只是读翻译——但如果当前请求的 lang 参数已被污染,且你没关掉多语言中间件,这个 lang() 调用仍会触发 Lang::load(),从而重放一次目录穿越。
安全做法是:要么彻底关闭多语言开关,要么在验证器中显式指定语言标识,例如 lang('validate.required', [], 'zh-cn'),避免读取用户传入的 lang 值。
最易被忽略的点是:漏洞修复不是“升级完就完事”,而是“升级 + 关功能 + 改中间件 + 查日志 + 清缓存”一整套动作。少一步,lang=../../../../usr/local/lib/php/pearcmd 就可能变成 RCE 入口。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











