文件上传漏洞
2018年,文件某知名社交平台的上传安全团队在例行巡检时发现了一个奇怪的现象:服务器上多出了一个从未见过的JSP文件,文件名是漏洞一串随机字符。打开一看,文件里面只有一行代码——一个简单的上传文件写入指令。攻击者已经拿到了这台服务器的漏洞控制权,而入口仅仅是文件一个看起来毫不起眼的头像上传功能。
这就是上传文件上传漏洞的典型面目。它不靠什么高深的漏洞内存溢出或者逻辑绕过,就是文件利用程序对用户上传文件的"过度信任"。你让用户传图片,上传用户传了个脚本;你以为他会老老实实传.jpg,漏洞他偏要传.jsp。文件而服务器如果照单全收,上传还把文件放在Web目录下能直接访问的漏洞位置,那基本等于把家门钥匙插在锁上。
问题的根子在于:文件上传功能天然就是一条从外部通向服务器内部的通道。任何允许上传的接口,本质上都是在说"你可以往我的地盘放东西"。放什么、放哪里、放了之后能不能执行——这三个问题只要有一个没管住,漏洞就来了。
最常见的翻车方式
很多开发者的第一反应是检查文件扩展名。黑名单?把.php、.jsp、.asp拉黑。这招几乎没用。攻击者可以传.phtml、.php5、.jspx,甚至利用大小写绕过——.PhP在某些系统上照样能被解析。更别提Windows系统还会自动去掉文件名末尾的点和空格,你检查的是"shell.php.",系统实际保存的是"shell.php"。
白名单看起来靠谱一些:只允许.jpg、.png、.gif。但问题来了,你检查的是文件名后缀,还是文件真实内容?如果只查后缀,攻击者把一句话木马命名为"avatar.jpg"就混过去了。如果同时检查Content-Type,也不保险——HTTP请求头是客户端可控的,改一下就行。
真正有效的做法是检查文件头(Magic Bytes)。JPEG文件开头是FF D8 FF,PNG是89 50 4E 47。但即便如此,如果服务器配置不当,比如Apache的mod_php把.jpg也当作PHP解析,或者Nginx的fastcgi_split_path_info配置有误导致路径信息被截断,那检查文件头也白搭。
一个真实的绕过案例
某次渗透测试中,目标系统只允许上传.zip文件,并且上传后会解压到指定目录。看起来挺安全?攻击者构造了一个包含"../../../webroot/shell.jsp"路径的zip文件。解压时,路径穿越直接把木马写到了Web目录。这种漏洞叫Zip Slip,影响过无数使用压缩包上传功能的系统。
另一个经典场景是文件包含配合上传。系统本身不允许上传PHP,但存在一个本地文件包含漏洞。攻击者先上传一个内容为PHP代码的.jpg,然后通过包含漏洞去包含这个图片文件。PHP解析时不管文件后缀,只要内容里有<?php ... ?>就执行。上传+包含,两个中危漏洞组合起来就是高危。
防御不是加个函数就完事
我见过太多代码只写了一句if (!in_array($ext, $whitelist)) die('不允许');就以为万事大吉。真正的防御得从多个层面下手:文件类型用白名单+文件头校验,文件名重命名为随机字符串,存储目录禁止执行权限,上传目录和Web根目录分离。Nginx里加一条location ~* /uploads/.*\.(php|jsp|asp)$ { deny all; },Apache里关掉上传目录的Options Indexes和AllowOverride。
还有一点常被忽略:上传后的文件访问权限。如果上传目录可以被遍历,攻击者能直接看到你存了什么。如果上传的文件名可预测,配合其他漏洞就能精准打击。这些细节堆在一起,才构成一道像样的防线。
文件上传漏洞之所以常年排在OWASP Top 10里,不是因为它有多复杂,而是因为它太容易被忽视。一个功能上线时,大家关注的是"能不能传上去",而不是"传上去之后会发生什么"。等到服务器上多出一个从没见过的文件时,往往已经晚了。
(责任编辑:热点)
