一个正常的上传漏洞图片上传功能,怎么就成了服务器沦陷的上传漏洞入口?答案藏在文件名的后缀里。
2019年夏天,上传漏洞某中型电商平台被拖库,上传漏洞起因是上传漏洞运营人员图省事,在商品评价模块直接用了开源的上传漏洞上传组件。攻击者上传了一个名为
shell.php.jpg的上传漏洞文件。后端校验逻辑只检查了 MIME 类型为image/jpeg,上传漏洞放行。上传漏洞但 Apache 的上传漏洞mod_mime模块解析文件名时,遇到多个扩展名,上传漏洞从右往左匹配,上传漏洞.jpg不识别为可执行脚本,上传漏洞继续往左看,上传漏洞.php被识别了。上传漏洞文件落地,访问,执行。一个system($_GET['cmd'])就拿到了 www-data 权限的 shell。这类漏洞的根子不在代码写得多烂,而在“信任边界”放错了位置。前端 JavaScript 校验
accept="image/*",攻击者抓包改一下 Content-Type 就绕过。后端如果只依赖$_FILES['file']['type'],那更是形同虚设——这个值来自 HTTP 请求头,客户端说是什么就是什么。真正可靠的判断只有两种:读取文件头部的魔数(magic bytes),或者用 getimagesize() 这类函数实际解析图像结构。但即便校验通过,文件存到磁盘上,如果 Web 服务器配置不当,依然可能被解析执行。我见过最离谱的一个案例,是某政府网站的上传点把文件存到了
/upload/目录,但 Nginx 配置里location ~ \.php$的匹配范围是整个站点。攻击者上传了一个.php文件,虽然上传接口白名单只允许.jpg .png .gif,但攻击者利用 Windows 环境下shell.php::$DATA的流特性绕过了后缀检查。文件实际写入后,Windows 自动去掉::$DATA,变成shell.php。Nginx 一匹配,直接执行。这个漏洞在 IIS 6.0 时代更猖獗,shell.asp;.jpg这种分号截断的玩法,现在听起来像上古传说,但换了个壳依然活着。防御措施其实不复杂,但需要多层配合。文件类型校验用
finfo_file()读魔数,别信任何客户端提交的元数据。文件名重命名成随机字符串加安全后缀,比如a7f3c9.jpg,彻底丢掉用户可控的部分。存储目录给 755 权限,且通过 Nginx 的location规则禁止该目录下任何脚本执行。如果业务必须保留原始文件名,那就把文件存到 Web 根目录之外,用后端脚本读文件流再输出。有个细节容易被忽略:图片二次渲染。把用户上传的图片用 GD 库或 ImageMagick 重新生成一遍,哪怕里面嵌了 PHP 代码,经过像素级重绘也会丢失。这个方法还能顺带干掉图片马。但要注意 ImageMagick 自身的历史漏洞,比如 ImageTragick,别防了一个坑掉进另一个坑。
上传漏洞的本质是“文件内容”与“文件解释方式”之间的错位。服务器以为自己在存图片,解释器却把它当代码跑。堵住这个错位,比堆砌 WAF 规则实在得多。
顶: 4142踩: 4681






评论专区