Laravel 8 文件上传安全配置需设独立非 webroot 磁盘路径、白名单约束子目录、禁用 move() 直传并校验路径合法性。

在 Laravel 8 中为文件上传设置安全的磁盘路径与相对路径,核心是避免硬编码、防止路径穿越、确保 web 用户可写且不可直接访问原始存储目录。直接修改 storage_path() 函数无效,必须通过配置驱动层和权限控制实现。
配置自定义磁盘并指定安全 root 路径
打开 config/filesystems.php,在 'disks' 数组中新增一个磁盘(如 'secure_uploads'),明确指向服务器上独立、非 webroot 的物理路径:
① 使用 storage_path() 拼接子目录时,必须保证该子目录不在 public/ 下,例如:'root' => storage_path('app/uploads')
② 若需完全脱离 storage/ 目录(如存到 /var/www/shared/uploads),则写绝对路径:'root' => '/var/www/shared/uploads'
③ 【关键前提】 确保该路径所属用户组与 web 服务器进程(如 nginx 或 www-data)一致,且权限设为 755 或 775(组可写)。执行:sudo chown -R :www-data /var/www/shared/uploads && sudo chmod -R 775 /var/www/shared/uploads
④ 不要将 root 设为 public_path('uploads')——这会导致文件直接暴露在 web 根目录下,绕过 Laravel 权限控制。
使用 storeAs() 时强制限定相对路径范围
上传逻辑中禁止拼接用户输入的完整路径,必须用白名单方式约束子目录层级:
方法一:固定前缀 + 时间戳哈希$path = $request->file('avatar')->storeAs('users/' . md5(auth()->id()) . '/profile', $filename, 'secure_uploads');
方法二:预定义目录映射表
$allowedDirs = ['avatar', 'document', 'contract'];<br>if (!in_array($type, $allowedDirs)) { abort(403); }<br>$path = $request->file('file')->storeAs($type . '/' . now()->format('Y/m'), $filename, 'secure_uploads');
【易错点】 若传入 storeAs('../etc/passwd', ...) 这类含 .. 的路径,Laravel 默认不校验——必须靠上述白名单或正则过滤拦截,否则触发路径遍历漏洞。
禁用 move() 直传,统一走 Storage 门面
直接调用 $request->file()->move() 会跳过所有磁盘驱动、事件监听与路径安全检查,极易被利用写入任意位置。
✅ 正确做法:始终使用 Storage::disk('secure_uploads')->put() 或 storeAs(),它们自动对路径做标准化处理(如移除 ../、转义斜杠)。
❌ 错误示例:$request->file('logo')->move('/tmp/', 'logo.png'); // 绝对路径直写,危险!
这一步操作起来很简单,但后果严重——一旦攻击者控制文件名或路径参数,就能覆盖系统关键文件。
验证上传文件是否落入预期磁盘根目录内
在控制器中上传后,立即校验生成的物理路径是否仍在预设范围内:
第一步:获取实际写入的绝对路径
$disk = Storage::disk('secure_uploads');<br>$fullPath = $disk->path($path); // 如 /var/www/shared/uploads/users/abc123/profile/photo.jpg
第二步:比对是否以配置的 root 开头
$root = $disk->getAdapter()->getPathPrefix(); // 获取 config 中定义的 root 值<br>if (strpos($fullPath, $root) !== 0) { throw new RuntimeException('File written outside allowed directory'); }
第三步:确保路径中不含 .. 或空字节等非法字符if (str_contains($path, '..') || str_contains($path, "\0")) { abort(400); }


















