必须采用finfo_file()校验真实mime类型、强制映射合法后缀并重命名、禁用上传目录脚本解析:仅校验后缀或content-type极易被伪造绕过,如shell.php.jpg可被apache解析执行。

用户上传一个名为shell.php.jpg的文件,后缀是.jpg,服务端仅校验后缀就放行,结果Apache按双后缀规则解析执行其中的PHP代码,服务器立刻被植入WebShell——这不是理论风险,而是每天都在发生的线上事故。
后缀校验单独使用为什么失效
ext规则只读取$_FILES['file']['name']中最后一个点之后的内容,完全不打开文件看一个字节。攻击者把恶意PHP脚本命名为1.php.jpg,后缀提取结果就是jpg,直接通过白名单。
浏览器、curl、Burp Suite都能任意构造文件名,【后缀纯属客户端输入,和文件真实内容毫无关系】。
更危险的是服务器解析层差异:Nginx默认不解析双后缀,但Apache会从右往左匹配,.php.jpg最终按.php执行;IIS甚至支持.asp;.jpg这种分号分隔的绕过写法。
仅靠MIME字段校验同样不可信
方法一:用$_FILES['file']['type']判断
这个值来自HTTP请求头的Content-Type字段,浏览器可随意修改。传一个shell.php,手动把Header设为image/jpeg,后端就当它是图片收了。
方法二:ThinkPHP的mimeTypes验证规则
它底层比对的就是$_FILES['file']['type'],不是读文件内容。配置mimeTypes:image/jpeg,image/png,攻击者改个请求头照样绕过。
【所有依赖客户端提交字段的校验,本质上都是摆设】。
必须用finfo_file()读魔数做真实MIME校验
第一步:确认PHP已启用fileinfo扩展
运行php -m | grep fileinfo,无输出则需在php.ini中取消extension=fileinfo注释并重启PHP服务。
第二步:获取临时文件的真实MIME类型
调用$file->getMime()(ThinkPHP 6+封装)或原生finfo_file($finfo, $file->getRealPath()),传入的是tmp_name路径,不是移动后的路径。
第三步:严格白名单比对
必须用in_array($realType, ['image/jpeg', 'image/png'], true)精确匹配,禁止用strpos($realType, 'image/') === 0这种宽松判断——攻击者可伪造image/x-php绕过。
这一步操作起来很简单,但漏掉任一环节都会让整个校验失效:没启用fileinfo→返回false;传错路径→读不到内容;宽松匹配→放行伪装MIME。
后缀与MIME必须绑定映射并强制重命名
即使finfo_file()返回image/jpeg,也不能直接保留用户传来的.php.jpg后缀。要查预设映射表:['image/jpeg' => 'jpg', 'image/png' => 'png'],得出合法扩展名,再用uniqid() . '.jpg'生成新文件名。
用户原始文件名里的../.htaccess、shell.php%00.jpg、test.jpeg.php全部丢弃——【最终存储的文件名必须完全由服务端控制】。
映射表必须硬编码,不能动态推导。比如application/zip不能映射成.zip,因为Office文档(.docx/.xlsx)本质是ZIP,但业务可能不允许上传压缩包。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











