上传必须进行真实 MIME 类型校验而非仅依赖扩展名,使用 mimes 规则配合 finfo_file(),禁用不可信文件名,强制安全存储路径,PDF/Office 需内容净化,文件操作需与数据库事务闭环。

上传前必须做的 MIME 类型验证
仅靠文件扩展名判断类型极不安全,攻击者可将恶意脚本命名为 shell.php.jpg 绕过检查。Laravel 的 validate() 方法默认只校验扩展名,需配合 mimes 或 mimetypes 规则 + 服务端真实 MIME 检测。
- 在请求验证规则中明确指定允许的 MIME 类型,例如:
['mimes:jpg,jpeg,png,pdf'],这会触发 Laravel 调用finfo_file()读取文件头 - 避免使用
extensions:jpg,png单独校验,它不检查内容 - 若需支持 WebP,注意 PHP
finfo扩展需 ≥ 7.4,且部分旧系统可能识别为application/octet-stream,建议同时加mimetypes:image/webp - 自定义验证时,可用
Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix()获取临时路径后手动调用finfo_open()复核
存储路径与文件名必须完全可控
用户提交的原始文件名($request->file('avatar')->getClientOriginalName())含不可信字符、路径遍历风险(如 ../../etc/passwd)和编码问题,绝不能直接拼入存储路径。
- 始终用
store()或storeAs(),而非move():前者由 Laravel 生成安全哈希路径,后者易出错 - 生成唯一文件名推荐方式:
Str::uuid() . '_' . time() . '.' . $file->guessExtension(),guessExtension()基于 MIME 更可靠 - 禁止将用户输入用于目录名,如
storeAs('uploads/' . $request->input('category'), ...)—— 攻击者可传入category=../../config - 磁盘配置中设置
'root' => storage_path('app/uploads'),确保写入位置隔离,不暴露在 web root 下
PDF/Office 等文档需额外做内容安全处理
即使 MIME 验证通过,PDF 可嵌入 JavaScript、Office 文档可带宏,直接提供下载或前端渲染存在 XSS 或沙箱逃逸风险。
- 对 PDF 使用
ghostscript重写(剥离 JS 和注释):gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/screen -dNOPAUSE -dQUIET -dBATCH -sOutputFile=output.pdf input.pdf
- Office 文件(.docx/.xlsx)建议用
phpoffice/phpspreadsheet或phpoffice/phpword读取并重存,丢弃所有宏和 ActiveX 控件 - 若需预览,不要直接
,改用后端转成图片或 PDF 后再展示;或使用Content-Disposition: attachment强制下载 - 上传后立即用
exec('file -b --mime-type ' . escapeshellarg($path))二次确认实际类型,与验证时结果比对
数据库记录与清理策略要闭环
文件写入成功 ≠ 业务逻辑完成。未关联数据库记录或未清理失败临时文件,会导致磁盘泄漏或孤儿文件。
- 先保存文件,再写数据库;若 DB 写入失败,需主动删除已存文件:
Storage::delete($path) - 使用数据库事务时,文件操作无法回滚,必须手动补偿:捕获异常后立即删文件
- 定期清理无主文件:建立
file_uploads表记录disk、path、uploaded_at,用 Artisan 命令扫描storage/app/uploads中无对应记录的文件 - 上传接口响应中返回的 URL 必须走 Laravel 的
Storage::url(),而非硬编码路径,否则切换 CDN 或本地/对象存储时失效










