去年秋天,网站文件一个做电商的上传朋友半夜给我打电话,说网站被挂了博彩页面。漏洞我让他登服务器看了一圈,网站文件最后在`/upload/`目录里翻出一个叫`logo.php.jpg`的上传文件。打开一看,漏洞里面藏着一句话木马。网站文件攻击者没用什么高深手段,上传就是漏洞钻了上传接口的空子——前端校验只认扩展名,后端干脆没校验。网站文件这事不新鲜,上传但每年都有大把网站栽在上面。漏洞
文件上传漏洞的网站文件本质很简单:网站让用户传文件,却没管住传的上传是什么。一个正常的漏洞图片上传功能,如果后端拿到文件后直接按原名存进Web目录,攻击者就能传个`.php`、`.jsp`或者`.asp`上去。访问一下,服务器就当脚本执行了。等于给人家开了一扇后门,还是带钥匙的那种。
有人觉得,前端不是有JavaScript校验吗?改个后缀就绕过去了。抓包改文件名、改Content-Type、甚至直接构造POST请求,前端那点限制形同虚设。真正要命的是后端只检查了`Content-Type: image/png`,或者只看了文件头几个字节,却忘了处理文件扩展名和存储路径。我见过一个系统,上传时把文件重命名为`时间戳+原扩展名`,看起来安全,但攻击者传个`shell.php`,重命名后变成`1698765432.php`,照样执行。
更隐蔽的是解析漏洞。比如老版本的Apache,遇到`shell.php.jpg`这种文件名,会从右往左解析,发现`.jpg`不认识,继续往左,碰到`.php`就当成PHP执行。Nginx配错了`fastcgi_split_path_info`,`shell.jpg/x.php`也能触发。IIS 6.0那个经典的分号漏洞——`shell.asp;.jpg`——直接当asp跑。这些不是理论,是实实在在被人扫过的坑。
怎么防?别指望单一手段。扩展名白名单得有,但别只靠它。文件类型检测要结合文件头,比如JPEG的前两个字节是`FF D8`,PNG是`89 50 4E 47`。存储目录必须禁止脚本执行——Apache里写`php_flag engine off`,Nginx里配`location ~* /uploads/.*\.(php|jsp|asp)$ { deny all; }`。文件名重命名成随机字符串,不带任何用户可控的扩展名。上传目录和Web根目录分开,最好放到独立域名或对象存储里。
还有个容易忽略的点:上传接口的权限。有些网站上传后返回一个URL,但那个URL可以直接访问,甚至能遍历目录。攻击者传个`.htaccess`上去,里面写`AddType application/x-httpd-php .jpg`,然后传个`shell.jpg`,服务器就把它当PHP跑了。这种组合拳防不胜防,核心还是别让上传目录有执行权限,别让用户控制文件名和路径。
我见过最离谱的案例,一个后台的上传功能只允许`image/gif`,但攻击者传了个GIF89a开头的文件,后面跟PHP代码,服务器照样执行。因为后端只检查了Content-Type,没检查文件内容。后来他们加了`getimagesize()`,结果攻击者用`exif_imagetype`绕过的变种又来了。安全这事,从来不是加一个函数就完事的。
说到底,文件上传漏洞的根子在于信任。信任了前端传来的扩展名,信任了Content-Type,信任了文件头,信任了存储路径。每多信任一层,就多一个绕过方式。正确的做法是:上传的文件一律重命名,一律存到不能执行脚本的目录,一律用白名单校验扩展名和MIME类型,并且校验实际文件内容。别偷懒,别图省事。一个上传接口写不好,整个服务器都可能变成别人的肉鸡。