文件上传——Upload_Labs

发布于 2021-09-26  569 次阅读


写在前面

  文件上传这个漏洞是每一个网安人踏入安全圈的基础必须内容,许多的GetShell的操作都是基于这个漏洞进行的,在学习初期,这个漏洞当时已经做了很多,DVWA、upload-lads等等,但是没有很好的形成笔记记录下来一些踩坑和经历,而且upload_labs这个靶场有些题目并没有进行实验,所以这次算是补档了。

进入正题

Pass-01

实验过程

  Pass-01重点是在客户端使用JavaScript对不合法图片进行检查,所以非常简单,关闭JavaScript或者将PHP文件后缀改成允许的后缀,抓包再改回来。

image-20210926145053574

关闭JS:

image-20210926145526911

image-20210926145616092

image-20210926145623930

修改后缀:
  上传修改为jpg后缀的PHP文件

  修改后缀

image-20210926151055925

  上传成功

image-20210926150835860

源码

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

Pass-02

实验过程

  Pass-02重点是在服务端对数据包的MIME进行检查,所以非常简单,上传PHP文件将mime修改为对应JPG文件MIME或上传jpg文件修改后缀为php即可。
Content-Type字段为MIME类型

image-20210926151538984

MIME类型参考:文件类型MIME对照表

源码

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

Pass-03

实验过程

  Pass-03重点是在服务端对数据包的后缀名进行检查,禁止上传.asp、.aspx、.php、.jsp后缀文件,但是查看源码之后是发现匹配性的检查,也就是说黑名单,用不在名单中的同样可以解析的文件后缀名即可上传。

语言类型 语言可能解析的后缀
asp/aspx asp,aspx,asa,asax,ascx,ashx,asmx,cer,aSp,aSpx,aSa,aSax,aScx,aShx,aSmx,cEr
php php,php5,php4,php3,php2,pHp,pHp5,pHp4,pHp3,pHp2,html,htm,phtml,pht,Html,Htm,pHtml
jsp jsp,jspa,jspx,jsw,jsv,jspf,jtml,jSp,jSpx,jSpa,jSw,jSv,jSpf,jHtml

image-20210926161613493

源码

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

Pass-04

实验过程

  Pass-04重点是在服务端对数据包的后缀名进行检查,禁止上传.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.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后缀文件,相对于第三题就是完善了黑名单,可以说是完整的黑名单,所以说考虑其他办法。
  除去直接上传脚本,还可以选择上传.htaccess后缀的文件

绕过原理

  .htaccess文件(或者"分布式配置文件"),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法, 即在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。
  .htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
  所以说本题的思路就是上传覆盖.htaccess文件,重写解析规则,将上传的图片马以脚本方式解析。

注意

  .htaccess文件能够工作还需要进行一系列的Apache的配置,并且这种上传漏洞只能在Apache服务器才能使用。
  在Apache服务器下的Apache\conf下httpd.conf中设置 AllowOverride All ,才能使用.htaccess文件

image-20210926170044760

上传文件

.htaccess文件内容

1.第一种、虽然好用,但是会误伤其他正常文件,容易被发现

<IfModule mime_module>
AddHandler php5-script .gif #在当前目录下,只针对gif文件会解析成Php代码执行
SetHandler application/x-httpd-php #在当前目录下,所有文件都会被解析成php代码执行
</IfModule>

2.第二种、精确控制能被解析成php代码的文件,不容易被发现

<FilesMatch "evil.gif">
SetHandler application/x-httpd-php   #在当前目录下,如果匹配到evil.gif文件,则被解析成PHP代码执行
AddHandler php5-script .gif #在当前目录下,如果匹配到evil.gif文件,则被解析成PHP代码执行
</FilesMatch>

3.第三种、同1没太大区别:

<IfModule mime_module>
AddType application/x-httpd-php .gif
</IfModule>

  .htaccess文件内容:SetHandler application/x-http-php的意思是设置当前目录所有文件都使用php解析,那么无论上传任何文件,只要符合php语言代码规范,就会被当做PHP执行,不符合规则则报错。

image-20210926170920713

image-20210926170956924

  然后上传图片马或者其他允许的后缀名的文件。

image-20210926171138447

  我这里上传完成后,cmd.jpg并没有解析,检查了一下配置和其他的东西之后,确定是搭建靶场的时候的PHP版本选择了7.3.4而且还是nts版本,更换一下再试试。
  到最后依然是没有能够解决不解析的问题,查询了较多的资料后,有一说是因为PHP使用的是nts版本,要是用非nts的版本,但是我这边使用小皮搭建的靶场不清楚之后该怎么更改,如果说能够找到设置好的靶场靶机的话,后续会继续研究。

源码

$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.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

后续

  在后续的研究过程中,我找了upload-labs的github项目,发现这个靶场的作者做了一个不需要进行其他设置的绿色版解压即用的环境,拿来安装之后进行测试,免去前面的上传,直接把文件放在网站后台,惊喜的发现完全OK了,不论是图片木马还是PHP文件抓包更改为图片后缀名,这个算是结束了。
  详细对比一下我自己靶场的环境设置,对比如下表:

自己的靶场环境 c0ny1的靶场环境
PHP版本 5.5.9-nts 5.2.17
PHP组件 缺少php_exif扩展 php_gd2,php_exif

  再结合其他大佬的一些文章,感觉最终的问题还是在PHP的版本,最为主要的就是nts非nts的区别
  另外就是怎么设置小皮面板使用他所提供的PHP之外的版本的PHP的方法,也是c0ny1大佬靶场的一个.bat文件提供的思路,将php.ini文件的路径进行更改,假如你已经安装了想使用的版本的PHP,设置对应的php.ini文件的路径即可,这里粘出来c0ny1大佬写的.bat文件的内容,因为我并没有写过这样的文件,所以看的也是一知半解,提供给你一个思路,如果你懂,请开始你的操作!

@echo off
set /P strOld=<current_path.txt
set strNew=%cd%

echo ***** modify phpStudy.ini *****
set phpStudyPath=phpStudy.ini
setlocal enabledelayedexpansion
for /f "tokens=*" %%i in (%phpStudyPath%) do (
  set "var=%%i"
  if not !var!.==. (
    set "var=!var:%strOld%=%strNew%!"
    echo !var!!>>%phpStudyPath%.bk
  )
)
move /y %phpStudyPath%.bk %phpStudyPath%
echo phpStudy.ini修改完毕!

echo ***** modify httpd.conf *****
set httpdPath=.\Apache\conf\httpd.conf
setlocal enabledelayedexpansion
for /f "tokens=*" %%i in (%httpdPath%) do (
  set "var=%%i"
  if not !var!.==. (
    set "var=!var:%strOld%=%strNew%!"
    echo !var!!>>%httpdPath%.bk
  )
)

move /y %httpdPath%.bk %httpdPath%
echo httpd.conf修改完毕!

echo ***** modify php.ini *****
set phpPath=.\PHP\php.ini
setlocal enabledelayedexpansion
for /f "tokens=*" %%i in (%phpPath%) do (
  set "var=%%i"
  if not !var!.==. (
    set "var=!var:%strOld%=%strNew%!"
    echo !var!!>>%phpPath%.bk
  )
)

move /y %phpPath%.bk %phpPath%
echo php.ini修改完毕!

echo %cd%> current_path.txt
pause

Pass-05

实验过程

  Pass-05重点仍然是在服务端对数据包的后缀名进行检查,相对于Pass-04来讲,Pass-05将黑名单完善至.htaccess文件,所以要另选办法。
  Pass-05的绕过是通过.user.ini文件进行绕过的
.user.ini文件

  php有很多配置,并可以在php.ini中设置。在每个正规的网站里,都会由这样一个文件,而且每次运行PHP文件时,都会去读取这个配置文件,来设置PHP的相关规则。

PHP_INI_*模式 含义
PHP_INI_USER 可在用户脚本(例如ini_set())或Windows注册表(自PHP5.3起)以及.user.ini中设定
PHP_INI_PERDIR 可在php.ini,.htaccess或httpd.conf中设定
PHP_INI_SYSTEM 可在php.ini或httpd.conf中设定
PHP_INI_ALL 在任何地方设定

  这样看着可能还是不太清楚,举个最简单的栗子——环境变量。系统环境变量和用户环境变量,诶,这样是不是一下就反应过来了,php.ini文件就像是系统环境变量一样,针对网站整体的相关配置进行设置,而.user.ini文件就像用户环境变量只针对与某个“用户”,这里特指某个网站的某个文件夹及其子文件夹,并不是针对网站的某个使用用户。
  上面的这四种分类呢,主要是针对于某些配置及对应的设定更改的动作要在哪里进行的分类,除了PHP_INI_SYSTEM以外的模式(包括PHP_INI_ALL)都是可以通过.user.ini来进行设置,和php.ini不同的是,.user.ini是一个能被动态加载的ini文件。也就是说我修改了.user.ini后,不需要重启服务器中间件,只需要等待user_ini.cache_ttl所设置的时间(默认为300秒),即可被重新加载。
  那么.user.ini实际的作用是什么呢?实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)
  其中有两个配置,可以用来制造后门:

auto_append_file
auto_prepend_file

  auto_prepend_file指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中即可。

auto_propend_file=test.jpg
auto_append_file=test.jpg

上传文件

  上传.user.ini文件

image-20210927173241914

  上传对应.user.ini文件中名字的图片木马

image-20210927173454179

  然后到这里你就会发现:诶?为什么没有解析?,这里我看了一眼源码,发现上传的文件全部都会被重命名,所以说,第一步上传.user.ini文件的时候上传上去的ini文件已经被重名了,结果就是.user.ini上传上去之后就失效了,要考虑其他的办法,可以考虑一下有没有题目带有文件包含啊什么的……
  结果呢,Pass-14中有一个文件包含漏洞,正常解析了。不过,这个肯定不是这一题的既定套路,所以说还是要研究一下。

后续

  后来对这题的源码进行了详细的研究,嗯……黑名单……嗯……Windows环境……嗯……解析漏洞!这里的解析漏洞特指是大小写绕过的解析漏洞,利用的是Windows系统不区分大小写的漏洞,上传过程中抓包更改后缀名的大小写,这样黑名单就无法匹配进行拦截了。
  这题思路错了就还挺尴尬的,但是.user.ini文件上传的思路也就放在这里了,后面如果有用到的题我再设置跳转过来。

插一句

  以上呢是我犯傻的时候做的,具体为什么会出现这个原因呢,是因为我手里当时的upload-labs靶场还是20关版本,而看的参考是21关的版本,作者在原来的基础上增加了一个新的Pass-05,也就是利用.user.ini绕过的题目,所以呢上面的方法是可行的,先上传.user.ini文件,然后再上传图片木马,根据.user.ini设定的内容,jpg文件会按照PHP文件进行解析。
  看着之前的内容本来想着要不删掉吧,但是这也是我踩坑的经历经留着了,就这样吧!摆烂了!
  具体为什么要这么做——使用.user.ini,因为本关的提示是./upload路径下有一个PHP文件——readme.php,但访问后并没有解析,而是以文本的形式展示出来,这部分可以自行验证,所以本关缺的就是一个解析规则——.user.ini

源码

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

Pass-06

实验过程

  Pass-06的重点是属于Windows系统的一个特性的解析漏洞,就是常说的Windows系统的大小写不敏感,通过此点进行防护绕过。
  解析漏洞基础原理就是文件在某种格式下,会被执行为该脚本语言的文件,这个漏洞的类型有很多种,这里碰到就列出来,碰不到的就自己搜索一下,网络上的文章挺多的。

文件上传

  很简单,更改后缀名加入大小写即可。

image-20210929111634085

源码

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

Pass-07

实验过程

  Pass-07重点仍然是在服务端对数据包的后缀名进行检查,相对于前两题,丰富了大小写的检查,代码中添加了将所有后缀名转换成小写的操作,并且有文件名重命名,这题的解题思路依然是解析漏洞,不过这次比较简单,只需要在文件名最后加一个空格即可。
  这个绕过方式准确来说也是Windows系统的特性,如果靶场建立在Linux下,同样是不适用的。

上传文件

后缀名后添加空格

image-20210930104648481

  上传成功

image-20210930110031329

源码

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

Pass-08

实验过程

  Pass-08重点仍然是在服务端对数据包的后缀名进行检查,不过这一题禁止了所有的可以解析的后缀,解题思路依然是利用Windows系统的特性——"."后的文件后缀名会被清除

上传文件

  这里只要有"."就可以了,加不加第二个后缀名都可以

image-20210930110153450

  上传成功

image-20210930110351848

源码

$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-09

实验过程

  Pass-09重点仍然是在服务端对数据包的后缀名进行检查,但是对比前几题源码中可发现缺少了对::$DATA的限制,这也就是本题的解题思路

几个"::$DATA"绕过的举例

上传的文件名 服务器表面现象 生成的文件内容
test.php:a.jpg 生成test.php
test.php::$DATA 生成test.php test.php内容
test.php::$INDEX_ALLOCATION 生成test.php文件夹
test.php::$DATA.jpg 生成0.jpg test.php内容
test.php::$DATA/aaa.jpg 生成aaa.jpg test.php内容

上传文件

  文件名后添加::$DATA

image-20210930111713890

  上传成功

image-20210930111833889

  这里需要注意一下,下方图片的路径会带有::$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 = 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 . '文件夹不存在,请手工创建!';
    }
}

阶段总结

  Pass-05到Pass-08都是利用Windows系统的特性进行绕过的题目,这里简单总结一下相关的Windows系统的特性。

文件名示例 含义
test.php. 文件名后加点"."
test.php(空格) 文件名后加空格
test.php:1.jpg 文件名后加冒号":"
test.php::$DATA 文件名后加NTFS ADS特性::$DATA
test.php::$DATA...... 文件名后::$DATA......

  这些特性可以统一归整为不符合Windows系统文件命名规则的行为,而这些行为则会触发Windows系统的特性——系统会自动去掉不符合命名规则的符号后面的内容
  当然,在Linux系统中也有相关的命名规则

linux命名规则

  1. 文件名最大长度为255
  2. 全路径长度最大为4096(16级最大文件长度)
  3. 区分大小写
  4. 除“/”之外所有字符都可以使用
  5. Linux不以文件扩展名区分文件类型,对Linux来说一切皆文件。
文件名示例 最终文件名
123.\php 123.php
234.php;.jpg 234.php
345.php&&.jpg 345.php

Pass-10

实验过程

  Pass-10重点是从黑名单绕过转变到白名单绕过,但是从源码看来还是一道黑名单的题目,最终的payload中不出现允许的后缀也是能够上传,所以说最终还是黑名单绕过。这题简单来说叠个Buff,不过准确来说我觉得是满足一次判断条件或文件后缀名被修改后剩下的能够使用系统特性或者其他的漏洞进行配合,所以这里使用. .的后缀名进行绕过。
  后一个点是为了保护前面进行绕过的". "被删除掉,这里在源码中明确点出有进行删除文件名末尾的点的操作,然后这里并没有使用白名单所以后面的绕过就跟黑名单绕过一致了。

上传文件

image-20211010165718328

源码

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

Pass-11

实验过程

  Pass-11重点是检测到在名单中的后缀名是会将后缀名删除,所以这题使用双写的方式进行绕过。

文件上传

image-20211010171751576

源码

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

Pass-12

实验过程

  Pass-12第一次开始使用白名单,这道题也算是开始白名单绕过方法的联系。提示中提示上传路径可控,上传之后会生成随机名称的命名文件,并且返回的文件路径时可以看出来是上传路径和文件的拼接,观察数据包之后对URL可以进行操作,所以可以使用%00对后续的拼接进行截断。
  在url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束。%00的截断方法是有使用条件的,在PHP版本小于5.3.4才可以使用,大于此版本的PHP环境如果想要使用此方法需要将PHP的magic_quotes_gpc更改为OFF,这函数是魔术引号,会对敏感的字符进行转义,"空"就会被转义加个反斜杠。

文件上传

image-20211010173541087

源码

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

Pass-13

实验过程

  Pass-13与Pass-12基本没有区别,只是将可控路径从URL中移动到了数据包中。

上传文件

image-20211010180234891

  这边有点需要注意的,BurpSuite在更新迭代中hex等选项卡被删除掉,合并在了"INSPECTOR"选项卡中。
  具体操作:在可控路劲中自己想更改为0x00的位置随意输入一个字符作为标志,选定此字符,打开"INSPECTOR"选项卡,Selection中有选中的内容,在CODE一栏中将HEX更改为00,最后点击Apply changes就可以完成修改了。

源码

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

阶段总结

  白名单是目前来说广泛使用的文件上传的防护手段,简单有效,绕过方法较少,绕过难度较大,一般来说都是配合其他漏洞进行上传比如说解析漏洞,其他能够对文件进行解析的漏洞,例如文件包含等,或修改文件名的相关漏洞,例如部分反序列化漏洞,这里较为有名的就是ThinkPHP5的反序列化漏洞,这个漏洞在2020年网鼎杯中被作为做为一道相当有难度的Web类考题。这里简单总结一下Upload-labs中对于白名单的绕过姿势。

%00截断

  这个准确来说是在路径可控的情况下进行的,也就是说在路径中添加%00对生成的路径进行后续的截断,让最后的路径和文件成为自己想要的。

127.0.0.1/upload/cmd.php(%00)/random-name.jpg

%00截断——hex升级版

  这个绕过方式就是在普通版本上在hex中添加%00截断。
  对比普通版本不同的是普通版本要多输入几个字符还要用url-decode别打我!
  其实这两者的差别是在可控路径的位置上,如果可控路径在URL中也就是数据包的HTTP头中,那么就要使用%00截断,这里不需要url-decode,而可控路径在数据包中Data部分时就需要hex升级版,也可以使用%00截断,当然需要对%00进行url-decode,要不然会报错。

双文件上传

  这个写在这里其实有点靠前了,因为这个方法在upload这个靶场里相对靠后的位置,简单说一下吧,在后面还会有详细的操作。
  就是上传过程中遇到这种数据包时就可以考虑双文件上传,两种处理方式。

image-20211010203433758

  第一种处理方式就是将上图框选的部分复制一份并将相应的动心进行更改,以相同的格式放在这个下面即可。

image-20211010203612372

  第二种方式就是将整个表示文件的部分复制下来,上面的是JPG,下面的是PHP,因为有些网站在开发过程中会有疏忽没有限制上传只能上传一个文件,所以此方法也是可以试一下的。

image-20211010203829030

Pass-14

实验过程

  Pass-14开始使用图片木马进行白名单绕过,而这一题比较简单,也并不算是真正的制作图片木马,而是使用文件头进行制作相关恶意文件进行绕过,并且可以使用文件包含漏洞使图片木马进行解析,所以不需要对文件后缀名进行操作。当然这部分的操作在后续的题目中都是会进行的。

图片木马制作

  1. 工具

  图片木马制作十分简单,工具很多,不过我这边没有收集这个东西,因为Windows自带有可以制作图片木马的东西——copy命令,Linux系统中可以使用的则是——cat命令

copy cmd.php/b+psb.jpg/a shell.jpg          #Windows
cat cmd.php psb.jpg > shell.jpg              #Linxu
  1. 文件头制作

  图片木马能够有效应对的是文件头检测这种防御方式,本质上图片木马可以等同于在文件头部添加上图片的文件头,让系统误认为上传上来的文件就是图片文件。
  利用文件头制作相对就很简单,在脚本文件的首行添加上相应的图片文件头即可

各类图片文件文件头参考
1.JPEG
- 文件头标识 (2 bytes): $ff, $d8 (SOI) (JPEG 文件标识)
- 文件结束标识 (2 bytes): $ff, $d9 (EOI)
2.TGA
- 未压缩的前5字节 00 00 02 00 00
- RLE压缩的前5字节 00 00 10 00 00
3.PNG
- 文件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A
4.GIF
- 文件头标识 (6 bytes) 47 49 46 38 39(37) 61
- 对应字符G I F 8 9 (7) a
5.BMP
- 文件头标识 (2 bytes) 42 4D
- 对应字符B M
6.PCX
- 文件头标识 (1 bytes) 0A
7.TIFF
- 文件头标识 (2 bytes) 4D 4D 或 49 49
8.ICO
- 文件头标识 (8 bytes) 00 00 01 00 01 00 20 20
9.CUR
- 文件头标识 (8 bytes) 00 00 02 00 01 00 20 20
10.IFF
- 文件头标识 (4 bytes) 46 4F 52 4D

11.ANI
- 文件头标识 (4 bytes) 52 49 46 46
- 对应字符 R I F F

上传文件

image-20211010191316784

  最后利用本题所提供的文件包含漏洞对上传的文件进行包含即可

image-20211010191446940

image-20211010191439223

源码

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

Pass-15

实验过程

  Pass-15算是真正意义上的使用图片木马,本题是使用PHP语言中的getimagesize()函数对文件进行检测,检测文件是否是图片文件,之前的利用文件头制作图片木马的方法就会失效,所以要使用工具制作木马。
  这里制作过程就省略了,具体参考Pass-14,犯个懒,上传过程也省了,和Pass-14没有区别,不需要对数据包进行修改,同样本题提供文件包含漏洞,使用文件包含即可进行解析获取WebShell。

  getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。

——返回值为一个list:

  - 索引 0 给出的是图像宽度的像素值
  - 索引 1 给出的是图像高度的像素值
  - 索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM
  - 索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的 标签
  - 索引 bits 给出的是图像的每种颜色的位数,二进制格式
  - 索引 channels 给出的是图像的通道值,RGB 图像默认是 3
  - 索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如: header("Content-type: image/jpeg");

源码

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

实验过程

  Pass-16相对于Pass-15可以说是换汤不换药,使用exif_imagetype()函数对文件进行检测。

    exif_imagetype() 读取一个图像的第一个字节并检查其签名。
    如果发现了恰当的签名则返回一个对应的常量,否则返回 false。返回值和 getimagesize()返回的数组中的索引 2 的值是一样的,但这个函数速度快得多。

  ——相关预定义常量

  | 值 | 常量 |
  | :--- | :----------------------------------------- |
  | 1 | IMAGETYPE_GIF |
  | 2 | IMAGETYPE_JPEG |
  | 3 | IMAGETYPE_PNG |
  | 4 | IMAGETYPE_SWF |
  | 5 | IMAGETYPE_PSD |
  | 6 | IMAGETYPE_BMP |
  | 7 | IMAGETYPE_TIFF_II(Intel 字节顺序) |
  | 8 | IMAGETYPE_TIFF_MM(Motorola 字节顺序) |
  | 9 | IMAGETYPE_JPC |
  | 10 | IMAGETYPE_JP2 |
  | 11 | IMAGETYPE_JPX |
  | 12 | IMAGETYPE_JB2 |
  | 13 | IMAGETYPE_SWC |
  | 14 | IMAGETYPE_IFF |
  | 15 | IMAGETYPE_WBMP |
  | 16 | IMAGETYPE_XBM |

  摆个烂,剩下的都和Pass-13和Pass-14一样,不写了!!!

源码

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

实验过程

  Pass-17这一关是对图片文件重新进行渲染生成一个新的文件,仔细研究了一下这块东西比较多,当然这题的绕过的话还算是比较简单,但是重新渲染这个东西还是需要研究一下的,所以这一题这里先暂时放下,后续我会写在另外一篇文章中,到时候会做链接的。

源码

$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的图片文件!";
    }
}

后续

文件上传——图片二次渲染绕过

Pass-18

实验过程

  Pass-18是利用条件竞争进行上传,简单说一下条件竞争,条件竞争就是和机器比速度,服务器在文件上传上来之后会对文件内容进行检查,如果是恶意文件,那么就会删除,而从上传到删除的这个过程并不是前后操作,这中间会有短暂的延迟,所以利用这一点点的延迟让文件进行解析从而落地WebShell。
  本关提示中点出需要代码审计,本人PHP水平并不太行,所以的啃了一下,这关的代码主要的逻辑就是判断POST请求方式是否上传了文件,确定上传了文件之后再次进行判断,判断后缀名是否合规,如果合规重新命名上传文件,如果不合规就删除上传的文件并输出提示。
  所以这关的难点主要集中在速度和如何落地WebShell上,如何抢过机器的速度?俗话说“要用魔法打败魔法”,所以这关要用机器打败机器!至于如何落地WebShell,这个就很简单,PHP中有可以写文件的函数,将恶意木马文件的内容更改成这个就可了!

上传文件

<?php file_put_contents("shell.php","<?php phpinfo();?>");?>

  上传包含上面代码的木马文件,并访问,改文件会自动生成一个新的PHP文件shell.php,文件内容为<?php phpinfo()?>

image-20211015152533901

如何与机器拼速度

  前面已经说过了,要用魔法打败魔法,办法就是BurpSuite的自带的Intruder模块和Python脚本,这里使用Intruder模块进行快速发包,当然你也可以用Python脚本,如果你会写的话用Python对文件进行访问,因为本关是合规的文件才会重命名,不合规的会删除,所以不用担心文件重命名的问题。具体操作如下:

插桩

image-20211015153152595

自动列表

image-20211015153300700

然后简单写个Python脚本进行访问

import requests

url = "http://127.0.0.1/upload/shell.php"
headers = {
    "user-angent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
}
while True:
    test = requests.get(url=url,headers=headers)
    if test.status_code == 200:
        print("successs!")
        break
    else:
        print("failed!")

  最简单的访问性脚本即可,然后BurpSuite进行发包,Python进行访问,Python访问成功后即可通关。
  不需要多,一条成功即可

image-20211015154519085

结果

  最后访问一下shell.php试试,这个落地有的时候可能会不成功,多试几次就可以了,也有个办法就是改一下Python脚本,在返回码200之后访问一下落地的文件,基本上就OK了。

image-20211015155043886

源码

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

Pass-19

实验过程

  Pass-19是在Pass-18的基础上添加了白名单的防护,只能使用图片木马配合相关的解析漏洞进行上传。

上传文件

  我这里用了文件包含漏洞的利用方式,简单省事,上传过程中因为有对文件的重命名的操作,而本题的条件竞争就是卡在这一步访问到为改名的文件,此时文件加锁,改名失败然后再用文件包含漏洞进行利用即可。

image-20211018150857495

image-20211018145759373

image-20211018145920746

其他内容

  这里题有点小小的bug,就是上传上去的文件所保存的目录是网站的根目录下,并没有在./upload下,看了一下原作者的GitHub说是已经修复了,但是新版本依旧是存在这个问题。

image-20211016173012597

  这个问题是PHP代码中少了一个路径中的'/',修复方法如下:

# 第一处
# 本关文件夹下index.php文件中第17行更改为如下代码
$img_path = $u->cls_upload_dir .'/'. $u->cls_file_rename_to;

# 第二处
# 本关文件夹下myupload.php文件第145行更改为如下代码
if( move_uploaded_file( $this->cls_tmp_filename, $this->cls_upload_dir ."/". $this->cls_filename ) == false ){

# 第三处
# 本关文件夹下myupload.php文件第161行更改为如下代码
if( file_exists( $this->cls_upload_dir ."/". $this->cls_filename ) ){

# 第三处
# 本关文件夹下myupload.php文件第195行更改为如下代码
if( !rename( $this->cls_upload_dir . $this->cls_filename, $this->cls_upload_dir .'/'. $this->cls_file_rename_to )){

  喂!说你呢!注意缩进,别到时候说:诶?我粘贴的你的代码怎么不行啊?劳资给你一拳!

源码

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

  }
......
......
...... 
};

Pass-20

实验过程

  Pass-20的重点是在PHP语言的mova_uploaded_file()这个函数的一些缺陷上,该函数会忽略/.后的内容,所以这个缺陷就是本题的绕过方式,其实这个方法也等同于%00截断。

  move_uploaded_file()函数将上传的文件移动到新位置,若成功,则返回 true,否则返回 false。

  move_uploaded_file(file,newloc)

参数 描述
file 必需。规定要移动的文件。
newloc 必需。规定文件的新位置。

  本函数检查并确保由 file 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 newloc指定的文件。
  如果file不是合法的上传文件,不会出现任何操作,move_uploaded_file()将返回false。
  如果file是合法的上传文件,但出于某些原因无法移动,不会出现任何操作,move_uploaded_file()将返回false,此外还会发出一条警告。
  这种检查显得格外重要,如果上传的文件有可能会造成对用户或本系统的其他用户显示其内容的话。

文件上传

  在页面或者数据包中save_name字段将/.加上即可

image-20211018152658189

源码

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

Pass-21

实验过程

  Pass-21的重点是在几个数组操作函数上,由于判断的数组位置并不准确造成的此题的解题方法。
  本题的上传的业务流程是上传后先验证此文件MIME是否合法,然后判断文件名是否为数组,如果不是则以.为分界生成数组,然后取数组最后一个元素校验,正常情况下这里是生成一个二元素数组,最后一个元素为文件后缀名。
  具体问题就是在end()这个函数,他的含义是取数组的最后一个元素,所以在建立数组的时候最后一个元素是合法的即可绕过此题防护。
  除此之外就是此题虽然也用到了move_uploaded_file()这个函数,但是没有任何关系,可以不管。

  explode()函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
  explode(separator,string,limit)

参数 描述
separator 必需。规定在哪里分割字符串。
string 必需。要分割的字符串。
limit 可选。规定所返回的数组元素的数目。
可能的值:
- 大于 0 - 返回包含最多 limit 个元素的数组
- 小于 0 - 返回包含除了最后的 -limit 个元素以外的所有元素的数组
- 0 - 会被当做 1, 返回包含一个元素的数组

  end(array)函数,输出数组中的当前元素和最后一个元素的值

  reset(array)函数把数组的内部指针指向第一个元素,并返回这个元素的值。

  count(array)函数,计算数组中的单元数目,或对象中的属性个数

文件上传

  两处修改,一个是MIME处,一个是save_name,修改完成后即可上传

image-20211018155900919

源码

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

写在最后

  前前后后总共写了三周多了,终于搞定了!这也算是回顾一下最初学习时候的内容了,主要是我旁边坐着一个嗷嗷待哺的萌新,这也主要是为她写的。
  总的来说就这样吧,upload-labs这个靶场涵盖内容比较广泛——解析漏洞、文件包含漏洞、黑白名单、图片二次渲染、编程语言特性、各种绕过姿势等等,对于刚入行的萌新还是比较有帮助的,并且这个靶场在一定程度上还可以作为PHP代码审计的入门课程,和DVWA这个靶场一样,从简单的漏洞开始一点一点开始学习代审也是一个不错的选择。
  好了,这个就到这里了,希望对你能有所帮助!(╯°Д°)╯︵┻━┻