uploadedfile::move()前必须校验路径与权限:先用is_dir()和is_writable()检查目标目录,不存在则mkdir()创建;再白名单过滤文件名,禁用路径遍历;同时显式配置filetype表单的required和file验证约束,并排查upload_max_filesize等php及nginx上传限制。

UploadedFile::move() 前必须校验路径与权限
直接调用 $file->move($dir, $name) 是最常见出错点。它不检查 $dir 是否存在、是否可写,也不过滤 $name 中的路径遍历字符(如 ../ 或空字节)。一旦失败,抛出的异常是模糊的 "The file could not be uploaded",容易误判为 Symfony 层问题。
实际应分三步做:
- 用
is_dir($dir) && is_writable($dir)显式检查目标目录;若不存在,用mkdir($dir, 0755, true)创建(注意:不要依赖Filesystem::mkdir()自动递归,它不校验父级权限) - 对
$name进行白名单处理:丢弃原始扩展名,用pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION)提取后比对 MIME 白名单,再拼接哈希前缀(如sprintf('%s.%s', bin2hex(random_bytes(8)), $ext)) - 禁止任何用户输入参与路径拼接 —— 尤其不能把
$file->getRealPath()当作源路径去复制,那是临时文件,请求结束即销毁
FileType 表单字段的 required 和验证约束必须显式配置
很多人以为加了 FileType::class 就自动校验,其实不然。如果没设 'required' => true,表单提交空值时会跳过整个验证链,$form->isValid() 仍返回 true,导致后续 $file->move() 拿到 null。
验证约束也得手动加,仅靠前端限制毫无意义:
-
new File(['maxSize' => '10M', 'mimeTypes' => ['image/jpeg', 'image/png']])—— 注意maxSize单位是字符串,不能写10 * 1024 * 1024 -
mimeTypes必须和服务器实际识别一致;application/jpg是错的,正确是image/jpeg - 若允许上传 ZIP,别只写
application/zip,攻击者可伪造 ZIP 内嵌 PHP 文件,需配合解压后二次扫描
手动提取 UploadedFile 时要防 upload_max_filesize 截断
当用 Dropzone 或 fetch 上传大文件,$request->files->get('file') 可能返回 null,但 $request->getContent() 非空 —— 这说明 PHP 在解析阶段就因 upload_max_filesize 或 post_max_size 超限而丢弃了整个 $_FILES 数组,根本没进 Symfony 请求对象。
排查顺序必须是:
- 先确认
ini_get('upload_max_filesize')和ini_get('post_max_size')是否足够(比如设为'20M'),改完重启 PHP-FPM - Nginx 用户额外检查
client_max_body_size,否则请求连 PHP 进程都进不去 - 用
$file->getError() === UPLOAD_ERR_NO_FILE区分“没传”和“传失败”,前者是前端问题,后者才是服务端配置问题
上传后文件存储路径不能直接暴露在 web 目录下
把文件存到 public/uploads/ 看似方便,但只要攻击者上传一个 shell.php,再通过 URL 访问,就能直接执行代码。生产环境必须隔离。
可行方案只有两种:
- 存到
var/uploads/这类非 Web 可访问路径,用控制器代理下载(如/download/{id}路由中读取并输出,同时校验权限) - 若必须直链,用 Nginx 的
alias+internal指令限制访问,例如:location /uploads/ { internal; alias /var/www/project/var/uploads/; },然后 PHP 中用header('X-Accel-Redirect: /uploads/'.$safeName)触发 Nginx 内部转发
无论哪种,都别让上传文件名、路径、扩展名参与任何 exec、include、require 或 shell_exec 调用 —— 这是多数 RCE 漏洞的起点。










