文件上传之后端黑白名单绕过(二)

文件上传常见验证(理论部分)

后缀名验证

这种验证方式是通过 黑名单和白名单的方式进行的

黑名单:就是如果检查到上传文件的后缀为黑名单的内容,就不允许上传

白名单:只允许上传规定的文件类型后缀

比如我写一个黑名单 不允许php、jsp 等等这种类型的后缀文件上传

那么我上传的时候想上传一个1.php 被检测到就不允许上传。

再比如我写一个白名单,只允许png,jpg,gif这种图片类型的方式上传

那么我就只能上传带这种后缀的文件上传。

这就是黑白名单的区别。

文件类型验证

文件类型验证:mime类型验证 每个不同类型的文件他的mime值都不一样

比如png jpg gif 等图片类型文件上传 时 他的 mime格式就是image/png

mime2

php文件上传时就是这个

mime3

而有些过滤代码他会检测这个mime值 如果发现不是image/png类型的他就不给上传

比如这个代码就是检查你的mime值

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $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.'文件夹不存在,请手工创建!';
    }
}

这个时候我们绕过思路 就是上传一个php文件 然后抓包修改他的mime值为image/png即可实现绕过

mime4

成功绕过并且实现了解析代码

mime5

 

内容头验证

这个就是验证文件的头部

比如一个png图片 如果以文本打开

头1

就会发现有这个标识  所有的png文件都会有这个标识

再比如一个gif图

头2

这个就是gif类型的标识

所以一些过滤代码的检查思路就是检查你上传的文件头有没有这些图片格式才有的文件头

有了才让你上传

没有就不让你上传

所以绕过思路也很简单 就是上传一个php文件然后头部加上这些标识 就实现了绕过

特殊解析绕过

这一个过滤代码就是 他会检查 你上传的文件后缀 不允许有asp aspx php jsp 这样的后缀名

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

那么如何实现绕过,就是利用一些特殊的解析方式

比如php5 php3  phtml

这种格式再apache网站也可以被解析成功的

.htaccess解析绕过

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

限制条件:

  • php5.6以下不带nts的版本
  • 服务器没有禁止.htaccess文件的上传,且服务商允许用户使用自定义.htaccess文件
$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",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //收尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

可以看到这个过滤过的很严谨。基本所有的脚本格式都上黑名单了,这时候 就想到利用.htaccess

先看代码

<FilesMatch “jpg”>
SetHandler application/x-httpd-php
</FilesMatch>

先传一个这样的.htaccess到当前目录,然后在上传一个jpg格式的木马

这个代码的意思就是当前目录下只有有文件名里带jpg的文件就会被当成php去执行

这样就达到了绕过上传木马的效果。

大小写绕过

$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",".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'];
            $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); //转换为小写

少了这一句,就是把文件名全转换成小写

那么思路就来了,我们可以大小写进行绕过。比如1.php写成1.Php就实现绕过

上传后成功执行!

大小写

 

空格绕过

空格绕过跟小数点绕过就非常的巧妙了 利用了 操作系统的特性 就是如果你建立一个文件 比如叫1.php空格 操作系统会自动给你去除空格 变成 1.php    同理1.php.也会变成1.php

那么思路就来了,如果他的过滤代码没有删除空格 那么我们上传一个1.php空格 是可以绕过他的php的过滤 

然后他在保存的时候,操作系统会自动删除空格再保存再文件夹里,就达到绕过的效果

$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",".ini");
        $file_name = $_FILES['upload_file']['name'];
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        
        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 = trim($file_ext); //首尾去空

就是没有去除空格。

直接上传一个php文件 然后抓包添加空格

空格1

访问 成功执行!因为这里对上传文件进行了随机命名,所以再回应包里可以看到文件名

再次访问:

空格2

小数点绕过

原理跟空格绕过同理

$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",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        
        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);//删除文件名末尾的点

没有删除文件最后的.在进行哦匹配

所以上传一个php文件 抓包加. 然后保存时候操作系统就把.去掉了。

小数点1

再次访问 代码成功被解析执行。

小数点2

::$DATA”绕过

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

例如:”phpinfo.php::$DATA”Windows会自动去掉末尾的::$DATA变成”phpinfo.php”
利用条件:对方服务器必须是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",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = 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

那我们就可以利用 直接把php文件改为.php::$DATA 直接上传

但是我们直接改名的话 windos不让改

data绕过1

那就只能先上传然后抓包修改了。

data2

他还是对我们的文件名进行了随机命名,看响应包再次访问。

data3

代码成功解析,实现绕过。

基于单次过滤绕过

这个是啥意思的 这个纯属是一些程序员造成的漏洞,就是他们写过滤代码就写一次比如他们写过滤小数点 

我写一个php.空格.的后缀 他把.过滤了然后以后过滤完了就去过滤空格了,但是后面还有一个点,所以 还是能绕过。。

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

这个代码过滤的很全吧 刚才上述的方法貌似都绕过不了  但是要命的就是他只过滤了一次 。

所以:

单次过滤1

单次过滤2

还是实现了绕过并且执行

双写后缀绕过

这种过滤机制就是比如他检查你的文件名发现有php就给你删除了。

但是他缺陷还是只检查一次 所以我们可以写pphphp 这种后缀 他把中间的php删除后剩下的还是组成了一个php 从而实现绕过

过滤代码:

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

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

写一个8.pphphp文件上传成功解析

双写1

双写2

 

%00的Get方式绕过

00截断原理
谈到00截断我们都会想到,有什么0x00截断,%00截断,也有人对两个东西分析一大堆,那么它俩有什么区别呢,什么场合适用哪一个呢?这就要从00截断的原理说起:
其实截断的原理也很简单,无论0x00还是%00,最终被解析后都是一个东西:chr(0)
chr()是一个函数,这个函数是用来返回参数所对应的字符的,也就是说,参数是一个ASCII码,返回的值是一个字符,类型为string。
那么chr(0)就很好理解了,对照ASCII码表可以知道,ASCII码为0-127的数字,每个数字对应一个字符,而0对应的就是NUT字符(NULL),也就是空字符,而截断的关键就是这个空字符,当一个字符串中存在空字符的时候,在被解析的时候会导致空字符后面的字符被丢弃。
这种情况常出现在ASP程序中,PHP 版本<5.3.4时也会有这个情况,JSP中也会出现。
那么就可以知道00截断的原理了,在后缀中插入一个空字符(不是空格),会导致之后的部分被丢弃,而导致绕过的发生。如:在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php,而空字符怎么插入的呢?通常我们会用Burp抓包后,在文件名插入一个空格,然后再HEX中找到空格对应的16进制编码“20”,把它改成00(即16进制ASCII码00,对应十进制的0),就可以插入空字符了。PS:这里的空格纯粹只是一个标记符号,便于我们找到位置,其实这里是什么字符都无所谓,只不过空格比较有特异性,方便在HEX中查找位置

过滤源码:

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

这种过滤只允许上传jpg png gif 的图片类型了 

002

先正常上传发现会被改名

003

路径中假如%00就会被截断 文件就会命名问%00前的东西

001

实现绕过上传

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

post里需要url编码一下  刚才那个get

两个就这么个区别  具体原理 其实也不复杂 就是post属于数据包里的东西 服务器接受时候 都会 进行一次解码

所以我们要先编码一次

post 00

 

© 版权声明
THE END
喜欢就支持一下吧
点赞18 分享
评论 共1条

请登录后发表评论