三闸防线详解:白名单剔除、落盘前拒绝与 .htaccess 同口径
学完这篇你能搞清楚一次文件上传要过几道关、每道关各管什么,以及为什么只做一层校验迟早会出事。
文件上传是论坛最容易被拿下的入口。攻击思路说起来很朴素:把一段可执行代码塞进一个看起来像图片的文件,传上去,再想办法让它跑起来。所以防御不能只靠一层,要按「类型判断 → 落地时机 → 执行权限」排成三道闸。下面按顺序讲。
第一步:白名单剔除,先决定"什么东西根本不许进来"
这一步做的是准入判断,发生在任何字节被写进磁盘之前。
核心逻辑是"剔除"而不是"拦截":维护一份允许的扩展名清单,凡是取不到扩展名、或者扩展名不在清单里的,直接从候选里划掉,连讨论的余地都没有。用白名单而不是黑名单,是因为黑名单永远列不全(php5、phtml、pht、phar……),而白名单的边界是封闭的。
在 Clara BBS 里,这个清单有两个互相独立的字段,在后台「系统设置 → 上传」:
- 允许的扩展名:管图片,例如 jpg、png、gif、webp
- 附件允许的扩展名:管附件,例如 zip、rar、pdf、txt
注意:这两个字段不联动。很多人只改了"允许的扩展名"就往帖子里传 zip,然后一直失败——因为附件走的是另一个字段。图片上传还是会员权益,普通用户会看到"会员专属"提示,管理员豁免,这是两码事,别混。
取扩展名时要做归一化,否则形同虚设:
- 统一转小写,防止
.PHP绕过 - 以最后一个点为准,
shell.php.jpg得到的是jpg - 丢掉路径信息和空字节,防止
x.php%00.jpg这类花招 - 文件名里的中文、空格、特殊符号统一替换成安全字符
同时不要只看前端传过来的 MIME 类型——那是客户端说的,可以随便伪造。服务端判断类型要落到文件内容上,图片还要做二次校验(真正读一遍头部信息,确认它确实是张图片、尺寸解析得出来),校验不过照样剔除。
第二步:落盘前拒绝,失败时磁盘上不留任何痕迹
这一步管的是顺序。
正确的顺序是:收文件 → 全部校验通过 → 才写入 uploads 目录。任何一项不通过,直接在内存里结束,返回具体原因。
常见错误写法是先落盘再检查:
move_uploaded_file(...); // 先写进去
if (!在白名单里) unlink(...); // 再删掉
这中间有个窗口期。并发请求、异常中断、删除失败,都可能让一个非法文件留在目录里。既然最终要删,那干脆别写。
注意:落盘失败时页面会告诉你是哪一类原因,比如超过 PHP 上传上限、扩展名不在白名单、目录不可写——顺着提示排查,比反复试快得多。
如果落盘这一步就是失败,先查三件事:
- 白名单字段是否包含该格式(图片字段和附件字段分开看)
- PHP 的 upload_max_filesize 与 post_max_size 是否大于文件体积。宝塔默认 2M,手机直出的照片随便就超,建议调到 30M 以上
- uploads 目录是否可写
第三步:.htaccess 同口径,落地了也不让它跑起来
前两道闸的前提是"判断永远正确"。但安全设计不能建立在假设上,所以还要有第三道:假设某个文件真的落到了 uploads 目录,它也不能被执行。
做法是在上传目录放一份 .htaccess,关掉脚本执行能力,只允许静态读取。所谓"同口径",意思是这里的规则要和第一步的白名单对齐——白名单之外的类型不让落地,落地目录也不给任何执行权限,两层判断用的是同一套边界,不会出现"类型放行了但执行没关"或者反过来的错位。
Apache 环境下大致是关掉该目录的脚本处理器、限定只能读取常见静态资源类型这一类写法。Nginx 没有 .htaccess,等价做法是在站点配置的 location 里对 uploads 路径单独声明:不做 PHP 转发,只当静态文件发出去。用宝塔的话,面板里对应的就是站点的伪静态/安全配置项。
注意:伪静态那块别搞混。Clara BBS 会自动识别 .html 后缀的 URL,服务器只需要把非静态文件的请求转发到 index.php,整站层面不需要写一堆规则。上面说的 uploads 目录限制是独立的一条,别指望伪静态规则顺带解决它。
三道闸的关系可以一句话概括:**白名单决定谁能来,落盘前校验决定什么时候落地,.htaccess 决定落地之后能干什么。** 任何一层单独拿出来都不够看,三层叠在一起才是完整的闭环。
小结
- 白名单要"剔除"思维,边界封闭;取扩展名务必归一化:小写、取最后一个点、丢路径与空字节、重命名非法字符
- 图片扩展名和附件扩展名是两个独立字段,图片还要做内容级二次校验,不认客户端声明的 MIME
- 校验必须全部通过再落盘,杜绝"先写后删"的窗口期
- 上传失败页面会给具体原因,先按提示查白名单字段、PHP 体积上限、目录权限
- 上传目录要单独禁执行;Apache 用 .htaccess,Nginx 用 location 限制,和整站伪静态是两回事
- 会员权益、上传限制、文件类型是三套不同的开关,排查时别串线
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员






