upload-labs靶场1-19关详解

发布时间 2023-07-12 13:29:48作者: L00kback

upload-labs靶场下载地址

https://gitcode.net/mirrors/tj1ngwe1/upload-labs?utm_source=csdn_github_accelerator

需要新建一个upload文件夹,该靶场在php5.2.17版本下(除特殊说明的情况下)。

Pass-01(前端验证绕过)

先上传一个php文件看一下回显

image-20230710214420276

然后上传一个正常文件,发现回显正常。查看了一下网页源代码,发现上传文件检查是通过前端进行验证的,那么有很多种方法继续绕过。

源码分析

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

getElementsBy() 方法可以返回指定名称的对象的集合。

substring(起始索引,结束索引) 返回字符串的子字符串。

lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。

方法一

直接禁用js就可以上传了,以火狐浏览器为例,按f12,在‘调试器’面板最右边有个设置按钮,禁用js。

image-20230710215347828

然后把写好的php文件再次上传,发现没有提示文件错误。image-20230710215431493

访问http://localhost/upload-labs-master/upload/1.php,显示成功上传

image-20230710215639320

不过实战中直接禁用js插件会导致一些页面无法正常显示,这种只能打打靶场就行了。

方法二

右键查看网页源代码 — 复制全部代码 — 将代码放在一个txt文件中 — 将后缀改为html — 用编辑器打开 — 找到js代码并删除(删除红框里的代码)

在这里插入图片描述

然后在在上面添加一段action,地址是图片上传的地址,我们在这加上来

image-20230711090024373

然后用浏览器打开这个html文件,直接进行上传操作即可。然后上传一个在原页面上传个正常的图片,按F12,然后随便上传一个图片,在"网络"模块中可以找到上传地址

访问对应所上传的php文件即可。

方法三

新建一个png文件,写入<?php phpinfo();?>,然后burpsuite工具抓包,直接修改后缀名为.php然后点击Forward即可完成上传

image-20230711091749038

访问上传地址即可。

Pass-02(Content-Type方式绕过)

源码分析

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) { //判断是否点击上传按钮
    if (file_exists($UPLOAD_ADDR)) {   //判断UPLOAD_PATH文件路径是否存在   UPLOAD_PATH在config.php中定义为"../upload"
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {  //判断上传文件的类型是否为image/jpeg、image/png、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.'文件夹不存在,请手工创建!';
    }
}

攻击方法

这题主要考察对数据包MIME检查。上传一个1.php文件,通过抓包修改文件类型进行绕过。

image-20230711105716400

点Forward放包即可。

Pass-03(黑名单绕过)

源码分析

$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 . '文件夹不存在,请手工创建!';
    }
}
trim(string,charlist) 函数移除字符串两侧的空白字符或其他预定义字符。
deldot()函数,是作者自定义的函数,作用是删除文件名末尾的点。
strrchr(string,char)函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。
strtolower(string) 函数把字符串转换为小写。
str_ireplace(find,replace,string,count) 函数替换字符串中的一些字符(不区分大小写)
in_array(search,array,type) 函数搜索数组中是否存在指定的值。

这里可以知道黑名单array('.asp','.aspx','.php','.jsp'),但是并没有过滤phtml,php3,php4, php5, pht,可以通过这些后缀名进行黑名单绕过,但是要在httpd.conf配置文件添加下面这段

AddType application/x-httpd-php .php .phtml .php3 .php5
AddType application/x-httpd-php-source .phps

php版本为5.4.45,而不是5.4.45-nts

参考:https://blog.csdn.net/CSDN_caiqian/article/details/129493643

攻击方法

新建一个以.php5为后缀的文件,写入一句话木马,直接上传即可。这里需要通过F12网络模式查看文件路径

image-20230711200137590

访问即可。

image-20230711200156767

Pass-04(.htaccess文件绕过)

源码分析

$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,该文件可以把图片解析为php,需要在.htaccess里写入

<FilesMatch "1.png">   //xxx由自己定义,定义什么则上传相同文件
SetHandler application/x-httpd-php
</FilesMatch>

攻击方法

先上传.htaccess,再上传1.png

image-20230711200405676

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

黑名单把.htaccess给禁了,对比第4关的源码发现,缺少了$file_ext = strtolower($file_ext);将字符串转为小写。那么我们可以上传大写的文件后缀,创建1.Php上传。

攻击方法

新建1.Php,写入<?php phpinfo();?>,上传,访问即可。

image-20230711204149095

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

对比第4关,少了$file_ext = trim($file_ext);去除首位空白字符,既然最后一步没有去空操作,那么我们可以创建一个文件1.php,通过bp抓包修改文件名后缀包含空字符。

攻击方法

新建1.php,通过bp抓包

image-20230711204841894

放包即可。

image-20230711205001814

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

比较第4关,少了$file_name = deldot($file_name);删除文件名末尾的点,如1.php.,由于没有删除末尾的点,传到file_ext会是最后一个点后面的所以字符,那么就是空,空不在黑名单中,故可绕过。

攻击方法

新建一个1.php,通过bp抓包修改文件后缀。

image-20230711210256712

放包,访问路径即可。

image-20230711210347635

Pass-08 (文件后缀(::$DATA)绕过)

源码分析

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

比较第4关,少了$file_ext = str_ireplace('::$DATA', '', $file_ext);将文件后缀中的::$DATA字符串替换为空,这里利用了windows特性

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

例如:"phpinfo.php::$DATA"Windows会自动去掉末尾的::$DATA变成"phpinfo.php"

攻击方法

新建一个1.php,通过bp抓包

image-20230711211122340

放包,访问即可

image-20230711211220256

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

观察源码,比较第4关没有差别,仔细看下代码,发现没有循环验证,也就是说去除文件名末尾点只删除一次,那么我们是不是可以在后缀加两个点(两个点之间加个空格,因为deldot()函数是从文件名最后面开始判断是否为点,如果是则匹配下一个,如果两个点连在一起,则会一起被删除)进行绕过,显然是可以的。

附deldot()函数代码:

function deldot($s){
	for($i = strlen($s)-1;$i>0;$i--){
		$c = substr($s,$i,1);
		if($i == strlen($s)-1 and $c != '.'){
			return $s;
		}

		if($c != '.'){
			return substr($s,0,$i+1);
		}
	}
}

攻击方法

新建一个1.php,通过bp抓包

image-20230711215016847

放包,访问即可

image-20230711215049655

这里除了构造1.php. .还有1.phP. .1.php. . .都差不多。

Pass-10 (双写文件后缀绕过)

源码分析

$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);在file_name中查找deny_ext中的值,若找到则替换为空,很显然我们若构造1.pphphp,这会把中间的php给替换掉,则剩下的字符自动拼接成php,即可进行绕过。

攻击方法

新建一个1.pphphp,上传,然后访问地址即可。

image-20230711215838954

Pass-11 (get传参%00截断绕过)

源码分析

$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类型文件!";
    }
}
strrpos($_FILES['upload_file']['name'],".")函数查找文件名中最后一个点号(.)的位置。
substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1) 是使用 substr() 函数,从文件名中截取最后一个点号之后(不包括点号)的所有字符,即得到文件的扩展名。
date("YmdHis") 是获取当前日期和时间的格式化字符串,即时间戳
rand(10, 99) 是生成一个介于 10 和 99 之间的随机整数。

这里可以看到上传路径是可控变量,$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;,这里通过get传参给save_path赋值,但是我们可以通过抓包进行修改。在url中%00表示ascll码中的0 ,0x00是十六进制表示方法,也是ascii码为0的字符,在有些函数处理时,会把这个字符当做结束符。所以我们在save_path参数后面加%00截断后面的字符串,那么上传路径为/upload/1.php,即可上传成php文件解析。

不过%00截断是旧时产物,需要使用条件

php版本小于5.3.4
php的magic_quotes_gpc为OFF状态

攻击方法

新建一个1.png,写入<?php phpinfo();?>,然后上传抓包。

image-20230712090341210

放包,访问即可。

image-20230712090620209

Pass-12 (post传参%00绕过)

源码分析

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

这里唯一的不同就是save_path参数是使用post传参的形式,那么我们改成post传参即可。但有一点说明,gei传参会自动解码url编码后的字符,但是post传参不会,我们是传参时,需解码。

攻击方法

新建一个1.png,写入<?php phpinfo();?>,然后上传抓包。(选中%00右键点击Convert selection选中URL,有URLdecode解码)

image-20230712091951858

这里我用的2022.6.1版的bp,所以解码后是空白的,不过不影响,直接放包即可。

image-20230712092141315

Pass-13 (图片马绕过)

源码分析

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 = "上传失败";
        }
    }
}
@unpack("C2chars", $bin)  这行代码用于对二进制数据进行解包(unpack),将其转换为 PHP 的数据类型。
解包格式字符串 "C2chars" 的含义是:
"C" 表示解包为无符号字符(unsigned char)类型。
"2" 表示要解包两次,因为 2 是重复次数。
"chars" 是解包后的结果的键名。

intval($strInfo['chars1'].$strInfo['chars2'])  将解包后的无符号字符转换为整数类型

这里的意思就是上传的文件取内容的前2个字节进行判断,若是png、gif、jpg格式的则上传,否则不上传,那么我们需要使用图片?进行上传,在图片末尾写入一句话木马即可。

gif文件包含需在php5.4.45版本下

攻击方法

找到一个正常的图片和新建1.php,使用如下命令

copy logo.png /b + 1.php /a shell.png

意思是将1.php中的代码追加到logo.png中并重新生成一个叫shell.png的图片?。

image-20230712094114159

可以使用010工具打开,查看末尾是否有?。

image-20230712094225719

然后上传这个文件,因为是文件包含漏洞,需要在根目录下添加include.php文件,用于将图片?包含。

在include.php文件写入下列代码

<?php
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
    include $file;
}else{
    show_source(__file__);
}
?>

然后用file参数访问

image-20230712101006389

这里的图片名可通过F12中的网络模块获取。

Pass-14 (图片马绕过)

源码分析

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() 是 PHP 中用于获取图像文件信息的内置函数。它接受一个参数:图像文件的路径和名称,并返回一个包含图像信息的数组。
$type 数组中包含了多个索引,其中最常用的是:
$type[0]:图像宽度(以像素为单位)
$type[1]:图像高度(以像素为单位)
$type[2]:图像类型的常量值(例如 IMAGETYPE_JPEG 表示 JPEG 类型)
$type['mime']:图像的 MIME 类型

image_type_to_extension($info[2]); 这行代码用于将图像类型的常量值转换为对应的文件扩展名。

关键函数是这个getimagesize,getimagesize函数会对目标的十六进制的前几个字符串进行读取。比如GIF的文件头问GIF89a,png的文件头为塒NG。所以这关和第十三关一样,新建个图片?即可。

攻击方式

和13关一样,这里不赘述了。

image-20230712102610465

Pass-15 (图片马绕过)

源码分析

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($filename) 函数用于获取指定图像文件的类型,并返回对应的常量值

只获取文件类型,和前2关一样,图片?绕过

攻击方式

步骤一样。

这里需要修改php.ini配置文件,将 extension=php_exif.dllextension=php_mbstring.dll 前面的分号去掉。另外记得 extension=php_mbstring.dll 一定要放在 extension=php_exif.dll 的前面。

image-20230712103546304

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_ADDR.basename($filename);  //生成一个完整的上传路径  basename($filename) 从 $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";  //随机文件名  strval()将其随机整数转化为字符串
                $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的图片文件!";
    }
}
$im = imagecreatefrompng($target_path);:会使用上传的图片生成新的图片,此时会删除最后末尾的一句话木马

imagecreatefromjpeg()函数,二次渲染是由Gif文件或 URL 创建一个新图象。成功则返回一图像标识符/图像资源,失败则返回false,导致图片马的数据丢失,上传图片马失败。
按照前几关的方式上传,可以上传,但是包含漏洞无法解析。原因就是二次渲染将图片马里面的php代码删了。接下来把原图和修改后的图片进行比较,看哪里没有被渲染,在这里插入php代码。为了方便大家测试,这里再推荐网上大佬的二次渲染专用图片https://wwe.lanzoui.com/iFSwwn53jaf

攻击方法

我们先上传没有修复过的图片马上去,然后复制文件地址下载上传后的图片。用010打开两个图片进行比较

原图片马:

image-20230712113614427

渲染后的图片:

image-20230712113635740

可以看到原来的图片红色部分已被渲染,我们可以在已被渲染部分前添加一句话木马

image-20230712114028550

这里不可以在头部插入,会破坏gif文件头,验证图片失败,上传不了。可以保存一下,看看图片是否可以查看,若不行则该位置不能修改为一句话木马,需要找其他未被渲染部分修改。

修改后在上传,访问即可。image-20230712114302388

当然用我上面那个链接,大佬专门写的二次渲染图片,直接上传即可。

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($temp_file, $upload_file)在in_array()函数前判断,那么不就直接上传1.php文件不就行了吗,然而后面else中有unlink()删除文件,那么这种想法是不行的。这样的话,是不是可以在删除之前访问他,这样就无法删除了,相当于打开一个文件,然后在删除这个文件的时候显示文件在使用无法删除,思路很正确。

攻击方法

上传1.php,通过bp抓包,一直上传1.php这个文件。

在这里插入图片描述

如图进行修改

在这里插入图片描述在这里插入图片描述

start attack发包,然后用浏览器一直访问1.php,按F5一直刷新,如果在上传的瞬间访问到了,它就无法删除。

image-20230712122139076

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

查看代码,发现和第17关的差距是,这关还检测了后缀名,不能直接上传php文件,所以这关要上传图片马,其他步骤和17关类似。访问图片时使用文件包含。

攻击方法

和第17关一样的操作

image-20230712125916567

Pass-19 (move_uploaded_file()函数特性)

源码分析

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

这里没有大小写、点等禁用,而且只需要判断save_name的后缀是否在黑名单里,那么可以大小写绕过,点绕过,空字符绕过等。这一关主要不是考察这个知识点,是考察move_uploaded_file()函数的特性,这个函数会忽略掉文件末尾的/.,那么我们只需在文件末尾加上/.即可绕过。

攻击方法

上传一个1.php,通过bp抓包

image-20230712131421327

放包,访问即可

image-20230712131504007

注:move_uploaded_file()函数中的img_path是由post参数save_name控制的,所以这一关还可以在save_name利用%00截断,这个方法前面已经说过了不再赘述,注意php版本要小于5.3

总结

以上就是upload-labs靶场的所以通过技巧,我这里下的是老版本的upload靶场,新版本的有21关。新版本有很多配置问题我就没有通关了。实战中一般都是黑盒测试,这个靶场就是让大家了解有哪些方法去测试文件上传漏洞。希望能帮助大家的学习,才疏学浅,一般般。