必须先用basename()剥离路径、再拼接根目录并调用realpath()解析,严格校验路径前缀是否匹配白名单根目录(末尾带斜杠),最后提取扩展名与白名单比对,任一环节失败即拒绝访问。

用户提交的文件名参数若直接拼接进file_get_contents()或fopen(),极易触发路径遍历攻击,比如传入../../etc/passwd读取系统敏感文件。必须先用realpath解析真实路径,再严格校验是否落在允许目录内,最后才提取后缀用于类型判断。
第一步:剥离路径干扰,只保留原始文件标识符
对用户传入的$_GET['filename']或$_POST['id']执行basename()过滤,强制剔除所有斜杠、点号和上级目录符号。
$clean_name = basename($_GET['filename']);
这一步不能省——如果跳过basename直接拼接,攻击者传./config/../.env就能绕过后续校验。basename会把整个字符串当作文件名处理,自动截掉所有路径部分,只留下.env。
第二步:拼接安全根目录并解析真实路径
将清理后的文件名与预设的绝对根目录拼接,再调用realpath()获取规范化绝对路径。
$allowed_root = '/var/www/uploads/';
$full_path = realpath($allowed_root . $clean_name);
【realpath()返回false时绝不能当作“文件不存在”忽略】——它更可能表示路径越界或open_basedir拦截,此时应立即拒绝,而非继续执行。
第三步:验证路径是否真的在允许范围内
用strpos($full_path, $allowed_root) === 0严格比对前缀,确保解析后的路径以/var/www/uploads/开头。
必须用=== 0而不是!== false,否则/var/www/uploads2/evil.txt会被误判通过。
注意$allowed_root末尾必须带斜杠,写成'/var/www/uploads/',少一个/就会导致目录名前缀匹配失效。
如果校验失败,直接die('Access denied');终止流程。
第四步:提取扩展名并做二次类型校验
方法一:用pathinfo($full_path, PATHINFO_EXTENSION)安全获取小写后缀。
方法二:若需兼容无扩展名场景,用strrpos()定位最后一个点再截取,但必须加空值判断:
$ext = strrpos($full_path, '.') ? strtolower(substr($full_path, strrpos($full_path, '.') + 1)) : '';
拿到后缀后,必须与白名单数组比对,例如in_array($ext, ['pdf', 'jpg', 'txt']),禁止接受php、phar、svg等可执行或含脚本的类型。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











