upload-labs靶机练习


upload-lab

一、后缀黑名单绕过

1.前端 JS 绕过

可以直接更改 js 源码,允许上传 PHP 文件

也可以直接删除

2.只验证 Content-type

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'];

前端没有过滤,burp 抓包修改 content-type,绕过即可。

3.后缀名-黑名单绕过

源码

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

不得不承认,这波黑名单搞得我有点头痛。。
3.1

新知识学习

    用黑名单不允许上传.asp,.aspx,.php,.jsp后缀的文件
    但可以上传.phtml .phps .php5 .pht
    前提是apache的httpd.conf中有如下配置代码
    AddType application/x-httpd-php .php .phtml .phps .php5 .pht

可以在 apache 的配置文件中没有找到呀,但是好像也可以上传,但是访问上传文件时,自动将 php 脚本进行了 html 注释,没有任何显示
3.2

4.htaccess 绕过

源码

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",".ini");
        $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); //收尾去空

过滤是真的多。。。
提示

没有过滤.htaccess,可以尝试下


SetHandler application/x-httpd-php

创建一个.htaccess 文件,写入代码(内容为将 4.jpg 当做 php 文件解析)
上传.htaccess 文件,再上传 4.jpg 图片马文件(马文件是通过图片和 php 结合的)

5..user.ini 绕过黑名单

依然过滤了很多,过滤了.htaccess 后缀,但是没有过滤点.int 文件

所以可以上传.user.ini 文件
内容是
auto_prepend_file=test.jpg
让所有 php 文件都“自动”包含 test.jpg 文件

6.后缀名大小写绕过

没有对后缀名镜大小写转化。

7.空格绕过

源码

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",".ini");
        $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

相比前面的题,这道没有后缀名去空处理,加空格即可绕过。
注意这里仅仅是空格还是会有问题,需要在空格后面加上'
7.1

8.点(.)绕过黑名单

提示
8.1
后缀无法解析....

源码

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",".ini");
        $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); //首尾去空

相比前面的题,没有了$file_name = deldot($file_name);//删除文件名末尾的点,点号绕过
8.2

9.::$DATA绕过

源码

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",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = trim($file_ext); //首尾去空

相比前面的题,没有了$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA,
没有对后缀名中的’::$DATA’进行过滤
在php+windows的情况下
文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名
且保持"::$DATA"之前的文件名
9.1

10.点空点绕过

源码

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",".ini");
        $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;

主要区别是图片的存储路径是文件名,保存文件的时候没有重命名而使用的原始的文件名
可以使用1.php. .绕过
10

11.双写绕过

用 str_ireplace 把黑名单里的后缀全替换为空
但是这个函数只进行一次替换$file_name = str_ireplace($deny_ext,"", $file_name);
11

二、后缀白名单

12.Get%00 截断

提示
12-1

源码

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

可以看到 get 方法的 save_path 是可以控制的,可以使用%00 截断
我们可以抓包修改 get 的参数,然后通过 file_ext 无效,这样就可以上传 PHP 文件

%00 截断的概念和原理:

在URL中%00表示ASCII码中的0,而0作为特殊字符保留,表示字符结束
当url中出现%00时就会认为读取已结束,而忽略后面上传的文件或图片,只上传截断前的文件或图片
要求:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态

12

13.POST%00 截断

和上题的差距只是--由 GET 变成 POST 型
还是利用 00 截断
但是 POST 不会像 GET 对%00 进行自动解码
需要在二进制中进行修改
将 70 68 70 后面的 2b 改为 00
13

三、检查内容

14.图片马绕过

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

rb 是读取二进制文件

后面的 fread 函数只读两字节,也就是说只对文件头进行了检测

unpack(format,data)函数是规定在解包数据时所使用的格式,这里是文件头按照 c 格式解包

intval() 函数用于获取变量的整数值

但直接访问图片并不能把图片当做 PHP 解析
还需要利用文件包含漏洞
www/upload-labs/upload 目录下建立一个 php 文件

15.图片马绕过

  • 源码
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;
    }
}

使用 getimagesize()函数来获取图片的大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。。
仍然可以使用图片马绕过。

16.图片马绕过

源码

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

用 exif_imagetype 来判断文件的类型,仍然可以通过图片马绕过,成功上传,但是需要使用文件包含漏洞来访问对应文件

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的图片文件!";
    }
}
  • 判断了后缀名、content-type
  • 利用 imagecreatefromgif 判断格式
  • 二次渲染了上传的图片
  • 绕过方法:找到渲染前后没有变化的位置,然后将 php 代码写进去,就可以成功上传带有 php 代码的图片
    假设这里使用图片马绕过,上传之后的图片中的 PHP 语句会被过滤掉
    针对 gif 文件上传,做个尝试
    在 hex 代码中找到前后没有变化的内容,将添加到 1.gif 的尾部,即可成功上传。
    针对 png 和 jpg 的二次渲染上传,可参考大佬文章

18.条件竞争

源码

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

先上传文件,然后白名单判断文件的后缀名,如果不符,则报错及删除文件,如果在白名单,则重命名。
这就在文件的处理顺序上出现了问题,不管文件类型是否合格就上传至服务器,之后再对其类型进行判断,这样的处理顺序导致了在多线程的情况下,有可能对于不合格的文件还没来得及删除就已经被访问,导致不合格的文件绕过了限制

因此我们可以打个时间差:上传 1.php,只需要在它删除之前访问即可
可以利用 burp 的 intruder 模块不断上传,然后我们不断的访问刷新该地址

条件竞争:也就是先上传再判断是否需要删除。。。。