CakePHP 3如何校验上传图片扩展名并拦截篡改扩展名的非法文件?
CakePHP 3 图片上传校验实现方案
一、现有代码触发错误的原因
符合扩展名要求也进入else分支的问题,核心是以下几个逻辑错误:
- 两次重复校验扩展名,第一次做了小写转换,第二次用
pathinfo取扩展名时未做小写处理,碰到.JPG/.PNG这类大写扩展名的文件会直接校验失败 - 用
explode('.', 文件名)的方式取后缀,当文件名本身包含多个点(例如photo.2024.jpg)时,取到的索引1的内容并非真实扩展名,导致校验不通过 - 对用于校验的原始文件名提前使用
h()方法转义,可能破坏原文件名的扩展名结构,引发校验异常
二、完整实现方案(含伪造文件拦截)
要同时实现扩展名校验和伪造图片拦截,需要同时做后缀校验和真实文件类型校验,推荐使用PHP内置的finfo扩展判断真实文件类型,防止.exe等文件改后缀上传。
修正后的代码如下:
if (!empty($this->request->data['image']['name'])) { // 允许的扩展名和真实MIME类型 $allowedExt = ['jpg', 'jpeg', 'png']; $allowedMime = ['image/jpeg', 'image/png']; // 1、校验扩展名,统一转小写处理 $fileExt = strtolower(pathinfo($this->request->data['image']['name'], PATHINFO_EXTENSION)); if (!in_array($fileExt, $allowedExt)) { $this->Flash->error(__('请仅上传jpeg、png、jpg格式的图片')); return $this->redirect(['action' => 'edit', $id]); } // 2、校验文件大小(25M) if ($this->request->data['image']['size'] > 26214400) { $this->Flash->error(__('图片大小不能超过25M')); return $this->redirect(['action' => 'edit', $id]); } // 3、校验真实文件类型,拦截改后缀的伪造图片 $finfo = new \finfo(FILEINFO_MIME_TYPE); $realMime = $finfo->file($this->request->data['image']['tmp_name']); if (!in_array($realMime, $allowedMime)) { $this->Flash->error(__('上传的文件不是合法的图片文件')); return $this->redirect(['action' => 'edit', $id]); } // 4、生成文件名保存 $randNo = rand(0, 100); $filenameWithoutExt = pathinfo($this->request->data['image']['name'], PATHINFO_FILENAME); $saveName = $filenameWithoutExt . $randNo . '.' . $fileExt; $savePath = WWW_ROOT . 'uploads/users/' . $saveName; move_uploaded_file($this->request->data['image']['tmp_name'], $savePath); $newData['image'] = $saveName; }
代码说明:
- 去掉了重复的扩展名校验逻辑,统一用
pathinfo取扩展名并转小写,兼容大小写后缀 - 新增真实MIME类型校验,直接读取临时文件的实际内容判断类型,就算改了后缀也无法绕过
- 修复了带多个点的文件名取后缀错误的问题,用
pathinfo的PATHINFO_FILENAME取不带后缀的文件名更可靠 - 所有异常分支统一提前返回,逻辑更清晰
内容的提问来源于stack exchange,提问作者Yash Kumar Prasad
相关产品推荐
相关产品推荐

