必须服务端校验文件真实类型并控制上传目录权限:用 finfo_open 读文件头而非 $_files['type'] 或扩展名,白名单限定 mime 类型,重命名文件,禁用执行权限,上传后校验魔数及危险关键字。

上传前必须拦截:$_FILES 的 type 字段完全不可信
浏览器提交的 $_FILES['xxx']['type'] 是前端自填的 MIME 类型,随便改个 text/plain 就能绕过校验。真实类型得靠服务端读文件头判断。
- 用
finfo_open(FILEINFO_MIME_TYPE)读取实际二进制头,比扩展名更可靠 - 禁用
getimagesize()做类型判断——它不校验完整格式,恶意构造的 JPG 文件头后面塞 PHP 代码仍能通过 - 白名单严格限定:只允许
image/jpeg、image/png、application/pdf等明确业务需要的类型,别写image/* - ThinkPHP 自带的
validate(['fileExt' => 'jpg,png'])只校验扩展名,必须配合 MIME 校验一起用
ThinkPHP 文件验证规则怎么写才不漏掉危险路径
默认的 rule('fileSize', '0,2097152') 和 rule('fileExt', 'jpg,png') 看似够用,但攻击者会传 shell.php.jpg 或 ../public/shell.php 这类名字绕过。
- 启用
moveTo()前必须调用$file->isValid(),否则跳过所有验证直接移动 - 设置
rootPath为绝对路径且不在 Web 可访问目录下,例如/tmp/upload/,别用./uploads/ - 用
$file->getInfo('name')提取原始文件名后,强制重命名:md5(uniqid()).'.png',彻底丢弃用户传的文件名 - 验证规则里加
rule('fileName', '/^[a-zA-Z0-9_\-\.]+$/')拦截含路径分隔符或控制字符的名字
上传后还要再扫一遍:为什么不能只信上传时的校验
文件上传完成不代表安全结束。有些攻击利用解析漏洞(如 Nginx 的 .php.jpg 被当作 PHP 执行),或上传后被其他模块误当成可执行内容。
- 上传完成后,用
file_get_contents($path, false, null, 0, 4)读前 4 字节,比对 JPEG(\xFF\xD8\xFF)、PNG(\x89PNG)等魔数,排除伪装文件 - PDF 文件要额外检查是否含
/JS、/JavaScript关键字,防止 XSS 式嵌入 - 如果业务允许预览,别直接用
readfile($path)输出,先用header('Content-Type: image/png; charset=binary')显式声明类型 - 上传目录禁止执行权限:
chmod -R 644 /tmp/upload/ && chmod 755 /tmp/upload,确保连index.php都无法被 Apache/Nginx 解析
ThinkPHP 6.1+ 的 UploadFile 类哪些参数容易忽略
新版封装了部分逻辑,但几个关键参数不显式设置就等于没设防。
-
maxSize必须设为整数(单位字节),别传字符串'2M',否则可能被忽略导致大小失控 -
allowFiles是扩展名白名单,但和mimeTypes(MIME 白名单)必须同时配置,缺一不可 -
hash设为true会生成哈希子目录(如/a/b/c/xxx.png),缓解目录遍历和爆破风险 -
autoSub默认true,但若关掉,所有文件挤在根上传目录,容易触发 inode 耗尽或扫描攻击
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











