laravel原生validator无法直接验证文件内容是否为恶意脚本,仅校验元数据;必须在上传后、存储前用file_get_contents读取临时文件二进制流,结合正则扫描php等脚本特征,且不可信任前端传入的mime或扩展名。

直接验证文件内容是否为恶意脚本,Laravel 原生 Validator 不支持——它只校验元数据(如扩展名、MIME、大小),不解析或执行文件内容。真要防脚本,必须在上传后、存储前做内容扫描,且不能依赖前端传来的任何字段(包括 mime 或 extension)。
为什么不能只靠 mimes / mimetypes 规则
这些规则仅检查 HTTP 请求头或文件扩展名,极易被绕过:mimes:jpg,png 拦不住把 shell.php 改名为 avatar.jpg 的上传;mimetypes:image/jpeg 也拦不住 JPEG 文件末尾拼接的 PHP 代码。攻击者常利用这种“双扩展伪装”或“内容注入”触发 Web 服务器执行。
- 浏览器或工具可任意伪造
Content-Type头 - Laravel 的
file->getMimeType()底层调用 PHPfinfo,但若文件开头被刻意构造为合法 JPEG 签名(FF D8 FF),后面追加<?php system('id'); ?>仍会被识别为image/jpeg - 所有基于 MIME 或扩展名的规则,都只能作为第一道过滤,不能当作安全边界
如何用 file_get_contents + 正则检测常见脚本特征
对已上传但尚未存盘的临时文件,读取其原始字节流,用轻量正则扫描高危模式。适用于小文件(
- 先调用
$request->file('upload')->isValid()确保上传成功且未超限 - 用
file_get_contents($request->file('upload')->getRealPath())读取二进制内容(不要用__toString(),会丢失 null 字节) - 检测典型 PHP 脚本标记:
preg_match('/ - 检测 base64 编码的可疑 payload:
preg_match('/base64_decode\s*\(/i', $content) - 检测系统命令调用:
preg_match('/(exec|system|passthru|shell_exec)\s*\(/i', $content) - 若命中任一模式,调用
$fail('文件内容疑似包含恶意脚本')中断验证
示例内联闭包规则:
'upload' => ['required', 'file', function ($attribute, $value, $fail) {
if (!$value || !$value->isValid()) return;
$content = file_get_contents($value->getRealPath());
if (preg_match('/
<h3>图像类文件必须用 GD/Imagick 重渲染</h3>
<p>仅靠文本扫描不够:攻击者可将 PHP 代码藏在 JPEG 的 EXIF 注释区、PNG 的 IDAT chunk 或 GIF 的 LZW 数据中,这些区域在文本扫描时不可见,但某些 Web 服务器配置不当会执行它们。</p>
- 对
jpg、png、gif类型,强制用Intervention Image或原生 GD 重新编码:Image::make($file)->encode($file->getClientOriginalExtension()) - 该操作会剥离所有非像素元数据(EXIF、XMP、注释块),同时确保输出是干净的位图流
- 注意:GD 不支持 WebP 动画帧,若需支持 WebP,请用 Imagick 并显式禁用脚本解析:
$imagick->setOption('webp:skip-icc', 'true') - 重渲染后的文件再走
store(),原始临时文件自动销毁
生产环境必须配合 Web 服务器配置
PHP 层面的检测只是补丁,最终防线在 Nginx/Apache。即使你 100% 确认文件安全,也不能让上传目录具备执行权限。
- Nginx 配置中,对
/storage/app/public/uploads/路径,必须添加:location ~ \.(php|pl|py|jsp|sh|bash|exe)$ { deny all; } - Apache 需在对应目录启用
.htaccess:Deny from all,并禁用Options +ExecCGI - Linux 文件系统层面,确保上传目录属主为
www-data,权限为755,且不启用setuid或setgid - 如果使用云存储(如 S3),上传后立即设置对象 ACL 为私有,并通过 Laravel 控制器鉴权后用
Storage::response()输出,绝不暴露直链
最易被忽略的一点:本地开发时 php artisan serve 默认不加载 Nginx/Apache 的安全规则,恶意文件可能在开发机上意外执行——上线前务必在真实 Web 服务器环境中测试上传路径的访问控制。











