php 7.4 中仅用 basename() 或简单正则无法防御路径穿越,必须组合 urldecode()、realpath() 和严格前缀校验;realpath() 解析路径语义并解码变体,但须用 === 0 校验绝对路径前缀,且需关闭 php.ini 危险配置。

直接说结论:PHP 7.4 环境下,仅靠 basename() 或简单正则过滤用户传入的文件路径,完全挡不住路径穿越攻击;必须组合 urldecode()、realpath() 和严格前缀校验,缺一不可。
为什么 basename() + 正则还是会被绕过
很多人写 $file = basename($_GET['file']); 再加个 preg_match('/^[a-zA-Z0-9_.-]+$/') 就以为安全了。实际攻击者发 ../../../etc/passwd%00.jpg,basename() 返回 passwd%00.jpg,后续若用 pathinfo($file, PATHINFO_EXTENSION) 判断类型,%00 后内容被截断,扩展名校验失效;更糟的是,拼路径后传给 file_exists(),空字节可能被底层 C 函数截断,导致检查通过但实际读取了系统文件。
-
basename()不解析..、不处理 URL 编码、不展开符号链接,它只是字符串切片 - 正则白名单只管字符组成,不管路径语义——
..%2f(即../的编码)能轻松绕过 - 没做
urldecode()前就进正则或拼接,等于把编码绕过漏洞主动交出去
realpath() + 前缀校验才是唯一可靠防线
realpath() 是 PHP 层面唯一能真正解析路径语义的函数:它会递归展开所有 ../、./、多重斜杠、符号链接,还会解码 URL 编码变体(如 %2e%2e%2f),最终返回操作系统中的绝对真实路径。但它必须配合前缀校验才有意义——否则攻击者仍可构造指向合法目录外的路径。
- 先
$decoded = urldecode($_GET['file']);,再$safe_path = realpath('/var/www/uploads/' . $decoded); - 必须用
===严格比对前缀:if (strpos($safe_path, '/var/www/uploads/') !== 0) { die('非法路径'); } - 如果
realpath()返回false(路径不存在或无权限),说明输入异常,应直接拒绝,不能 fallback 到原始字符串 - 上传场景中,务必重命名文件:
$new_name = bin2hex(random_bytes(16)) . '.' . $ext;,避免依赖用户传入的文件名
php.ini 关键配置必须关掉危险开关
即使代码层做了路径校验,如果 php.ini 允许远程包含或执行任意路径,整个防线就形同虚设。PHP 7.4 下这些配置项必须显式关闭:
-
allow_url_fopen = Off:禁用file_get_contents('http://...')类远程读取 -
allow_url_include = Off:禁止include 'http://...'这类远程代码执行 -
disable_functions = include, include_once, require, require_once, file_get_contents, readfile, fopen(按需禁用,尤其动态包含场景) -
expose_php = Off:隐藏 PHP 版本信息,减少攻击面
路径穿越不是“加个过滤就完事”的问题,它是路径解析逻辑与操作系统行为之间的博弈。realpath() 的结果必须是绝对路径,且必须用 === 0 检查前缀——任何松散比较、任何 fallback 逻辑、任何未 decode 的中间态,都可能成为突破口。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











