文件上传功能几乎是所有 Web 应用的标配——头像、附件、导入数据,无一例外。但正是这个看似简单的功能,长期占据 OWASP Top 10 的高危位置。一旦攻击者能上传一个可执行的 WebShell,服务器就等于拱手让人。本文从攻击链的视角出发,逐层拆解前端校验、MIME 检测和内容检测的绕过手法,并给出对应的防御策略。
一、为什么文件上传漏洞如此危险
文件上传漏洞的本质是:应用允许用户将可控内容写入服务器可访问的位置,且未正确限制文件的类型或执行权限。当上传的文件是 .php、.jsp、.asp 等脚本文件,且该目录能被 Web 服务器解析时,攻击者即可执行任意代码。
一条典型的利用链是:
- 上传一个包含
system($_GET['cmd'])的 PHP 文件 - 通过 URL 访问该文件并传入命令参数
- 获得服务器 Shell,进一步提权、横向移动
理解绕过手法之前,先要明白防御者通常设置了三道关卡:前端 JS 校验、服务端 MIME 检测、文件内容检测。攻击者的目标就是逐一击破。
二、第一道关卡:绕过前端校验
前端校验是最弱的一环,因为它完全运行在攻击者可控的浏览器环境中。
2.1 禁用 JavaScript
最粗暴的方式:浏览器禁用 JS,或者使用 Burp Suite 拦截请求。前端校验代码根本不会执行,请求直接到达服务端。如果服务端没有二次校验,上传直接成功。
2.2 修改前端代码
通过浏览器开发者工具,直接删除 onsubmit 事件或修改 accept 属性:
<!-- 原始 -->
<input type="file" accept=".jpg,.png" onchange="checkExt(this)">
<!-- 修改后 -->
<input type="file">
2.3 抓包改后缀
即使前端校验通过,攻击者也可以在 Burp Suite 中将 shell.jpg 改回 shell.php 再放行。前端校验永远不能作为唯一防线。
三、第二道关卡:绕过 MIME 检测
服务端常见的做法是检查 Content-Type 请求头:
if ($_FILES['file']['type'] != 'image/jpeg') {
die('只允许上传图片');
}
3.1 直接修改 Content-Type
在 Burp Suite 中将 Content-Type: application/x-php 改为 image/jpeg 即可绕过。因为 $_FILES['type'] 来自客户端请求头,完全可伪造。
3.2 利用 MIME 解析差异
某些语言或框架对 MIME 的解析存在差异。例如,image/jpeg; charset=utf-8 在某些校验逻辑中可能被截断处理,导致判断失误。
四、第三道关卡:绕过内容检测
内容检测是最难绕过的,常见手段包括文件头检测、getimagesize()、图像二次渲染等。
4.1 文件头伪造
检测逻辑通常读取文件前几个字节判断类型:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $tmpFile);
攻击者可以在 PHP 文件开头加上 GIF89a:
GIF89a
<?php system($_GET['cmd']); ?>
这样 finfo 会识别为 image/gif,但文件仍可被 PHP 解析器执行。
4.2 图片马 + 文件包含
如果应用存在本地文件包含(LFI)漏洞,攻击者可以上传一个包含 PHP 代码的图片,再通过 LFI 包含它:
http://target.com/index.php?page=uploads/shell.jpg
此时图片中的 PHP 代码会被执行。
4.3 二次渲染绕过
某些应用会对图片进行二次渲染(如 GD 库的 imagecreatefromjpeg)。攻击者可以将恶意代码写入图片的注释段(COM 段) 或 EXIF 数据,这些区域在渲染后可能被保留。
4.4 竞争条件
如果应用先上传到临时目录再校验删除,攻击者可以在删除前的高并发窗口内访问该文件。使用多线程工具(如 Python 脚本)持续请求,有机会在校验完成前执行脚本。
4.5 解析漏洞配合
不同 Web 服务器的解析特性也是绕过点:
- Apache:
shell.php.jpg在某些配置下按.php解析 - Nginx:
shell.jpg/x.php可能触发 PHP 解析 - IIS:
shell.asp;.jpg分号截断
五、防御策略:纵深防御
单点防御必然被绕过,正确做法是分层设防。
5.1 白名单 + 重命名
$allow = ['jpg', 'png', 'gif'];
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allow)) die('非法类型');
$newName = bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($_FILES['file']['tmp_name'], '/uploads/' . $newName);
永远使用白名单,永远重命名文件。
5.2 内容检测
使用 getimagesize() 或 finfo 验证文件真实类型,并检查文件头魔数。注意:这仍可能被图片马绕过,需配合其他措施。
5.3 上传目录不可执行
这是最关键的防线。在 Nginx 中:
location /uploads/ {
location ~ \.(php|jsp|asp)$ {
deny all;
}
}
在 Apache 中通过 .htaccess 禁用脚本执行:
php_flag engine off
5.4 分离存储
将上传文件存放在独立域名或对象存储(如 OSS、S3)中,与主站完全隔离。即使上传了脚本,也无法在主站上下文中执行。
5.5 权限最小化
上传目录设置为只读、不可执行,Web 进程以低权限用户运行。
六、总结
文件上传漏洞的攻防是一场持续的博弈。攻击者的绕过手法从简单的前端禁用,到 MIME 伪造、文件头构造、二次渲染绕过、竞争条件,层层递进;防御者也必须从前端校验、服务端白名单、内容检测、目录权限、存储隔离多个维度构建纵深防御。
核心原则只有一条:永远不要信任用户上传的任何内容,包括文件名、类型和内容本身。 在此基础上,配合不可执行目录和独立存储,才能将风险降到最低。
未经允许不得转载:任鹏个人博客 » 文件上传漏洞的攻与防:绕过前端校验、MIME 检测与内容检测的多种姿势

