图片真图校验:getimagesize 在云存储链路里的作用
学会这篇,你就能在自己的上传链路里加一道「真图校验」——搞清楚 getimagesize 该放在哪一步、云存储直传时它为什么失效、以及校验通过之后还剩哪些坑。
第一步:先认清校验在链路的哪一环
一次图片上传,完整链路是这样的:
- 浏览器选文件 → 提交表单
- PHP 把文件收进系统临时目录(
$_FILES['file']['tmp_name']) - 后端做扩展名白名单校验(只管后缀,管不住内容)
- 后端做真图校验(读文件头,确认它真的是一张图)← getimagesize 在这一步
- 文件搬到本地
uploads/或推送到云存储 - 落库,记录 URL
关键点:getimagesize 必须在一个「文件还在服务器本地、磁盘路径可读」的瞬间执行。这也是第五步之前必须拦下来的原因——一旦文件已经进了云端,再想校验就得先把字节拉回来。
注意:如果你的站是「前端直传 OSS/COS」(浏览器直接拿签名往云上扔,不经过 PHP),那第 3、4 步根本不存在,这是最大的坑,第三步专门讲。
第二步:getimagesize 到底在看什么
$info = getimagesize($_FILES['file']['tmp_name']);
if ($info === false) {
exit('这不是一张有效的图片');
}
$width = $info[0];
$height = $info[1];
$mime = $info['mime']; // image/jpeg
$type = $info[2]; // IMAGETYPE_JPEG 常量
// 白名单比对,用常量而不是字符串拼
$allow = [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_WEBP];
if (!in_array($type, $allow, true)) {
exit('不支持的图片格式');
}
它的原理是读文件头字节(JPEG 的 FF D8 FF、PNG 的 89 50 4E 47、GIF 的 47 49 46 38、WEBP 的 RIFF....WEBP)来判定真实格式,完全不信任后缀名。所以你把 shell.php 改名成 shell.jpg,扩展名白名单能骗过,getimagesize 骗不过。
注意:别再拿
$_FILES['file']['type']当依据,那是浏览器自己填的,随便伪造。校验只认 getimagesize 的返回。
第三步:云存储链路里的两个坑
坑一:直传模式下后端拿不到文件。 解法有两条:
- 保守做法:改成服务端中转——先落到本地临时目录,跑完 getimagesize 再上传云端。多一跳流量,但最稳。
- 直传做法:用云存储的上传回调。回调触发时,服务端拉回该对象的前 1KB 做头字节校验,不通过就把云端对象删掉。
拉回头部字节校验的写法:
$fp = fopen($signedUrl, 'rb');
$head = fread($fp, 1024);
fclose($fp);
$info = @getimagesizefromstring($head);
if ($info === false) {
// 云端对象删除 + 记录日志
}
坑二:只看一次,不管后续。 云端对象、CDN 缓存都可能被替换。高安全场景可以加个计划任务,定期抽样拉回前 1KB 复检。
注意:
getimagesizefromstring传进去的字节流太短会返回 false,这跟「是假图」是两回事。别一律当假图拒掉,正确做法是读够 1KB 以上,或对返回 false 的情况退回完整文件再校验一次。
注意:不要直接
getimagesize('https://...')读远程 URL。它慢,而且等于给外部输入开了个 SSRF 口子——用户能控制 URL 就能探测你的内网。要读远程就老老实实 fopen + fread 头部。
第四步:getimagesize 管不了的三件事
- SVG:它是 XML 文本,getimagesize 永远返回 false。SVG 要走单独白名单,并且必须消毒(去掉
<script>、onload之类),否则就是 XSS 直通车。 - 多语言文件:一张合法 JPEG 里可以塞 PHP 代码,服务器若按
image/jpeg解析无所谓,但一旦被当成.php执行就完蛋。所以校验通过之后还要做:服务端决定最终文件名和扩展名(哈希命名,别用用户传的名字)、上传目录禁止执行 PHP、必要时重新编码压缩一遍。 - 超大图:getimagesize 只读文件头,很快,不用担心性能。真正的瓶颈是上传体积。
第五步:在 Clara BBS 上的实际配置与排查
Clara BBS 的安全基线里,「上传文件类型白名单 + 图片二次校验」就是这套逻辑。落到操作上:
后台「系统设置 → 上传」里有两个独立字段——「允许的扩展名」管图片、「附件允许的扩展名」管附件,改一个不影响另一个,新增格式要两处分别确认。
图片上传失败时,按这三步排查:
- 上面那两个扩展名字段是否包含对应格式;
- PHP 的
upload_max_filesize/post_max_size是否大于文件体积(宝塔默认 2M,手机照片经常超,建议调到 30M+); uploads目录是否可写。
页面会提示具体原因(超过 PHP 上限、白名单不符、目录不可写),照着提示改就行。
如果你的站用了「图片上传是会员专属权益」这类配置,普通用户会被拦在更前面一层;管理员账号豁免。
小结:
- getimagesize 靠读文件头判真实格式,比扩展名白名单可靠,但必须在文件还落本地时执行。
- 云存储直传会让后端失去校验时机——要么改服务端中转,要么用上传回调 + 拉回头部字节校验。
- 远程校验用 fopen + fread 读头部,别用 getimagesize 直接读 URL。
- 校验通过 ≠ 安全:SVG 要单独处理,上传目录禁执行,文件名由服务端生成。
- Clara BBS 后台「允许的扩展名」和「附件允许的扩展名」是两个字段,排查上传失败先看它们和 PHP 体积上限。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员






