文件上传漏洞总结

优采云 发布时间: 2022-06-18 03:04

  文件上传漏洞总结

  现在我们来到第二关,先看看有没有JS检查

  

  很可惜,没有"纸老虎",那我们只能另寻他法

  

  提示文件类型不正确,猜测是判断后端检测了上传文件的MIME类型.那我们要如何修改?首先使用burp抓包

  可以看到content-type现在是显示文本的一个类型,将其改为图片格式,绕过检查

  

  放包

  

  上传成功,之后的步骤和第一关一样连接蚁剑即可.

  源码:

  

  3.黑名单绕过

  黑名单绕过有比较多的类型:

  3.1 上传特殊可解析漏洞

  通常网页会给我们上传的内容加上黑名单,即有些特定后缀的文件无法上传,那我们要想办法绕过黑名单的检测

  常见的特殊后缀有:.phtml .phps .php5 .pht

  一般黑名单禁止上传php类型文件的时候,我们可以将其改为以上几种特殊后缀即可绕过检测.

  

  类似这样即可.源码:

  $is_upload = false;<br />$msg = null;<br />if (isset($_POST['submit'])) {<br />    if (file_exists(UPLOAD_PATH)) {<br />        $deny_ext = array('.asp','.aspx','.php','.jsp');<br />        $file_name = trim($_FILES['upload_file']['name']);<br />        $file_name = deldot($file_name);//删除文件名末尾的点<br />        $file_ext = strrchr($file_name, '.');<br />        $file_ext = strtolower($file_ext); //转换为小写<br />        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA<br />        $file_ext = trim($file_ext); //收尾去空<br /><br />        if(!in_array($file_ext, $deny_ext)) {<br />            $temp_file = $_FILES['upload_file']['tmp_name'];<br />            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;            <br />            if (move_uploaded_file($temp_file,$img_path)) {<br />                 $is_upload = true;<br />           } else {<br />                $msg = '上传出错!';<br />           }<br />       } else {<br />            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';<br />       }<br />   } else {<br />        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';<br />   }<br />}

  3.2 大小写绕过

  直接看源码:

  $is_upload = false;<br />$msg = null;<br />if (isset($_POST['submit'])) {<br />    if (file_exists(UPLOAD_PATH)) {<br />        $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");<br />        $file_name = trim($_FILES['upload_file']['name']);<br />        $file_name = deldot($file_name);//删除文件名末尾的点<br />        $file_ext = strrchr($file_name, '.');<br />        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA<br />        $file_ext = trim($file_ext); //首尾去空<br /><br />        if (!in_array($file_ext, $deny_ext)) {<br />            $temp_file = $_FILES['upload_file']['tmp_name'];<br />            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;<br />            if (move_uploaded_file($temp_file, $img_path)) {<br />                $is_upload = true;<br />           } else {<br />                $msg = '上传出错!';<br />           }<br />       } else {<br />            $msg = '此文件类型不允许上传!';<br />       }<br />   } else {<br />        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';<br />   }<br />}<br />

  和上一个知识点对比,看看有什么区别

  首先是这里的黑名单数量急剧增加,特殊后缀全都被ban了,其次很重要的一点:$file_ext = strtolower($file_ext);消失了.

  这个函数代表的是转换为小写字母,如果这个函数消失,并且服务器是区分大小写,这时候我们将php改为pHp即可绕过黑名单.

  3.3 陌生后缀解析绕过

  这个知识点并没有专门的一关,但是做题目的时候也经常会遇到.

  原理也很简单,当阿帕奇服务器无法解析一些奇怪的后缀的时候,例如.asfsdfsd这种,就会自动往前搜索可以使用的后缀名.

  比如1.php.asfsdsad,就会向前检索为1.php,利用这个特性可以绕过一些黑名单.

  3.4::$DATA 文件数据流传输

  原理:在后缀名加::DATA后会令后面的数据以文件流的形式进行传输,同时也能保持::$DATA前面的格式,在文件被系统解析后 这段字符会被系统去除(windows系统下)

  例如 1.php 抓包分析 可以修改成filename=’1.php::$data’ 实现上传注入后门文件

  查看源码:

  $is_upload = false;<br />$msg = null;<br />if (isset($_POST['submit'])) {<br />    if (file_exists(UPLOAD_PATH)) {<br />        $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");<br />        $file_name = trim($_FILES['upload_file']['name']);<br />        $file_name = deldot($file_name);//删除文件名末尾的点<br />        $file_ext = strrchr($file_name, '.');<br />        $file_ext = strtolower($file_ext); //转换为小写<br />        $file_ext = trim($file_ext); //首尾去空<br />        <br />        if (!in_array($file_ext, $deny_ext)) {<br />            $temp_file = $_FILES['upload_file']['tmp_name'];<br />            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;<br />            if (move_uploaded_file($temp_file, $img_path)) {<br />                $is_upload = true;<br />           } else {<br />                $msg = '上传出错!';<br />           }<br />       } else {<br />            $msg = '此文件类型不允许上传!';<br />       }<br />   } else {<br />        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';<br />   }<br />}<br />

  相比于之前的代码,少了$file_ext = str_ireplace('::$DATA', '', $file_ext);,这个函数的意思是删除'::$DATA'字符串.

  这时我们就可以使用::$DATA绕过.

  3.5点绕过

  原理:

  利用windows特性,会自动去掉后缀名中最后的".",可在后缀名中加"."绕过.

  $is_upload = false;<br />$msg = null;<br />if (isset($_POST['submit'])) {<br />   if (file_exists(UPLOAD_PATH)) {<br />       $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");<br />       $file_name = trim($_FILES['upload_file']['name']);<br />       $file_ext = strrchr($file_name, '.');<br />       $file_ext = strtolower($file_ext); //转换为小写<br />       $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA<br />       $file_ext = trim($file_ext); //首尾去空<br />        <br />       if (!in_array($file_ext, $deny_ext)) {<br />           $temp_file = $_FILES['upload_file']['tmp_name'];<br />           $img_path = UPLOAD_PATH.'/'.$file_name;<br />           if (move_uploaded_file($temp_file, $img_path)) {<br />               $is_upload = true;<br />           } else {<br />               $msg = '上传出错!';<br />           }<br />       } else {<br />           $msg = '此文件类型不允许上传!';<br />       }<br />   } else {<br />       $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';<br />   }<br />}<br /><br />

  对比,发现少了$file_name = deldot($file_name);

  deldot函数的作用是删除末尾的点.

  这时候就可以使用点绕过.

  3.6 双写绕过

  当面对in_array这种检测力度不高的函数的时候,它检测的是是不是完全一样.比如"php"和"php.123"对他来说就是不一样的,这时候我们可以使用陌生后缀来绕过.

  但是面对

  

  str_ireplace 函数的时候,他的检测力度高,若我们输入php9,会匹配黑名单第一个php,把你删的只剩下’9’一个字符.这时候我们只需要进行双写绕过即可.

  

  这样中间的php三个被删了,还剩下php,因为很多时候没有循环检测机制,所以就可以绕过了.源码:

  $is_upload = false;<br />$msg = null;<br />if (isset($_POST['submit'])) {<br />   if (file_exists(UPLOAD_PATH)) {<br />       $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");<br /><br />       $file_name = trim($_FILES['upload_file']['name']);<br />       $file_name = str_ireplace($deny_ext,"", $file_name);<br />       $temp_file = $_FILES['upload_file']['tmp_name'];<br />       $img_path = UPLOAD_PATH.'/'.$file_name;        <br />       if (move_uploaded_file($temp_file, $img_path)) {<br />           $is_upload = true;<br />       } else {<br />           $msg = '上传出错!';<br />       }<br />   } else {<br />       $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';<br />   }<br />}<br /><br />

  4. 上传.htaccess)

  查看提示:

  

  好家伙,这么多黑名单,不急,给大家介绍一个新的后缀 -- .htaccess

  我们先创建一个名为".htaccess"的文件,因为没有文件名只有后缀名,所以有时候可能在桌面新建不了,我们需要在文件夹中新建它.

  在文件中写入:

  SetHandler application/x-httpd-php

  这三行代码的意思是通过一个.htaccess 文件调用 php 的解析器去解析一个文件名中只要包含”shell”这个字符串的任意文件,所以无论文件名是什么样子,只要包含”shell”这个字符串,都可以被以 php 的方式来解析,是不是相当邪恶,一个自定义的.htaccess 文件就可以以各种各样的方式去绕过很多上传验证机制的文件后缀名随便写但不可以是黑名单里面的后缀,如shell.jpg.将其上传之后,.htaccess 文件调用php 的解析器去解析shell.jpg,从而让一句话木马生效.

  5. 00截断原理

  0x00是字符串的结束标识符,这样服务器检测到0x00后就停止了向后检测,后面的内容就等于被注释了.依靠这点可以帮助我们绕过检测.

  误区

  有很多人喜欢在文件名中加进行截断,但其实这种方式是不对的,比如攻击者构造文件名:admintony.phpa.jpg,在提取后缀名的时候遇到则认为字符串结束了,那么他提取到的后缀名会是.php,.php后缀又不允许上传所以上传失败了(这里有必要提一句,有人可能会说在一些情况下,截断文件名可以成功,这种案例你试一下是不是任意文件上传,西普的00截断实验就是一个任意文件上传的上传点,既然是任意文件上传又何必用00截断绕过呢?)

  正确用法

  那么00截断应该在什么时候使用呢?数据包中必须含有上传后文件的目录情况才可以用,比如数据包中存在path: uploads/,那么攻击者可以通过修改path的值来构造paylod: uploads/aa.php

  为什么修改path才可以,因为程序中检测的是文件的后缀名,如果后缀合法则拼接路径和文件名,那么攻击者修改了path以后的拼接结果为:uploads/aaa.php/*********.******,移动文件的时候会将文件保存为uploads/aaa.php,从而达到Getshell效果。

  我们先看源码再看实战:

  $is_upload = false;<br />$msg = null;<br />if(isset($_POST['submit'])){<br />   $ext_arr = array('jpg','png','gif');<br />   $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);<br />   if(in_array($file_ext,$ext_arr)){<br />       $temp_file = $_FILES['upload_file']['tmp_name'];<br />       $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;<br /><br />       if(move_uploaded_file($temp_file,$img_path)){<br />           $is_upload = true;<br />       } else {<br />           $msg = '上传出错!';<br />       }<br />   } else{<br />       $msg = "只允许上传.jpg|.png|.gif类型文件!";<br />   }<br />}<br /><br />

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

  $img_path是直接拼接,因此可以利用截断绕过.

  通过Burp Suite将save_path=../upload/ HTTP/1.1使用进行截断-> save_path=../upload/shell.php HTTP/1.1

  将文件后缀改为允许上传的后缀,在第一行路径处添加如图信息

  特别注意!!!

  00截断必需条件是php版本号在5.3.4以前(不能包含5.3.4版本),我之前试了很多次都无法成功,换了一关查看那个网页的靶场php版本超过5.3.4了,所以又换了一个靶场.而且php的magic_quotes_gpc要为OFF状态.

  另外,如果用的是post传输,我们需要多进行一步解码手段.

  

  记得写完要用burp自带的功能给它解码一下,因为get传输的时候会自动解码,而post传输的时候不会自动解码.

  6.图片马及图片马的二次渲染漏洞原理

  一般文件内容验证使用getimagesize()、exif_imagetype()检测(当然也可能只检测文件头),会判断文件是否是一个有效的文件图片,如果是,则允许上传,否则的话不允许上传。

  本实验就是将一句话木马插入到一个合法的图片文件当中,然后用文件包含漏洞得到连接地址。

  图片马的制作

  copy a.png /b + a.php /a 3.png<br />/b:指定以二进制格式复制、合并文件,用于图像或者声音类文件<br />/a:指定以ascii格式复制、合并文件用于txt等文本类文件<br /><br /><p>这句话的意思是将a.png和a.php合并为3.png</p>

  

  当然要特别注意:

  图片马不能单*敏*感*词*匹马就得到flag,需要和别的手段一起使用才行,比如文件包含漏洞或者条件竞争漏洞才行.

  源码:

  function isImage($filename){<br />   $types = '.jpeg|.png|.gif';<br />   if(file_exists($filename)){<br />       $info = getimagesize($filename);<br />       $ext = image_type_to_extension($info[2]);<br />       if(stripos($types,$ext)>=0){<br />           return $ext;<br />       }else{<br />           return false;<br />       }<br />   }else{<br />       return false;<br />   }<br />}<br /><br />$is_upload = false;<br />$msg = null;<br />if(isset($_POST['submit'])){<br />   $temp_file = $_FILES['upload_file']['tmp_name'];<br />   $res = isImage($temp_file);<br />   if(!$res){<br />       $msg = "文件未知,上传失败!";<br />   }else{<br />       $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;<br />       if(move_uploaded_file($temp_file,$img_path)){<br />           $is_upload = true;<br />       } else {<br />           $msg = "上传出错!";<br />       }<br />   }<br />}<br /><br />

  其他的两关都是类似,只是仅仅换了一下检测图片的函数,目的就是为了检测我们上传的是否是真正的图片.

  如果可以看到源码只检测文件头的话,我们只需要在一句话木马前加上 GIF89a,上传php类型的木马即可绕过检测.

  图片马的二次渲染二次渲染原理:

  在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。

  绕过:

  配合文件包含漏洞:将一句话木马插入到网站二次处理后的图片中,也就是把一句话插入图片在二次渲染后会保留的那部分数据里,确保不会在二次处理时删除掉。这样二次渲染后的图片中就存在了一句话,在配合文件包含漏洞获取webshell。

  如何判断图片是否进行了二次处理?

  对比要上传图片与上传后的图片大小,编辑器打开图片查看上传后保留了哪些数据

  特别注意!二次渲染我强烈推荐使用GIF格式的,因为jpg和png格式的二次渲染十分复杂,需要用到脚本,GIF的二次渲染算是比较简单.

  实验步骤

  1、创建一个a.php的脚本文件

  

  2、在cmd命令行中,使用以下命令把1.gif和a.php合成图片马2.gif

  

  3、使用010 Editor16进制编辑器查看2.gif内容,可以看出php代码已经插入图片马中了

  

  4、上传图片马,并复制图片链接进行查看

  

  

  5、右击复制图片,将图片下载到本地

  

  6、使用010 Editor16进制编辑器打开图片,发现末尾的php代码没有了

  GIF绕过二次渲染的方法,就是通过对比上传前和上传后的两个文件,如果说哪个位置,它的上传前和上传后的没有变,我们就把php一句话代码插入到这个位置

  7、使用010 Editor16进制编辑器打开上传前的文件和上传后的文件,并对文件进行比较:

  

  

  8.蓝色部分就是没有改变的地方,接着我们就可以将一句话木马插入蓝色部分,可以成功连接蚁剑.

  7.条件竞争漏洞漏洞原理:

  条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的,因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生。

  上传文件源代码里没有校验上传的文件,文件直接上传,上传成功后才进行判断:如果文件格式符合要求,则重命名,如果文件格式不符合要求,将文件删除。

  由于服务器并发处理(同时)多个请求,假如a用户上传了木马文件,由于代码执行需要时间,在此过程中b用户访问了a用户上传的文件,会有以下三种情况:

  1.访问时间点在上传成功之前,没有此文件。

  2.访问时间点在刚上传成功但还没有进行判断,该文件存在。

  3.访问时间点在判断之后,文件被删除,没有此文件.

  总结起来一句话,因为服务器要在短时间里处理大量内容,但是解析代码需要时间,所以就会有破绽出来,我们就是利用这短暂的破绽进行绕过.

  实验背景

  先看源码:

  $is_upload = false;<br />$msg = null;   //判断文件上传操作<br /><br />if(isset($_POST['submit'])){ //判断是否接收到这个文件<br />   $ext_arr = array('jpg','png','gif'); //声明一个数组,数组里面有3条数据,为:'jpg','png','gif'<br />   $file_name = $_FILES['upload_file']['name']; //获取图片的名字<br />   $temp_file = $_FILES['upload_file']['tmp_name']; //获取图片的临时存储路径<br />   $file_ext = substr($file_name,strrpos($file_name,".")+1); //通过文件名截取图片后缀<br />   $upload_file = UPLOAD_PATH . '/' . $file_name; //构造图片的上传路径,这里暂时重构图片后缀名。<br /><br />   if(move_uploaded_file($temp_file, $upload_file)){ //这里对文件进行了转存<br />       if(in_array($file_ext,$ext_arr)){ //这里使用截取到的后缀名和数组里面的后缀名进行对比<br />             $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext; //如果存在,就对文件名进行重构<br />             rename($upload_file, $img_path); //把上面的文件名进行重命名<br />             $is_upload = true;<br />       }else{<br />           $msg = "只允许上传.jpg|.png|.gif类型文件!"; //否则返回"只允许上传.jpg|.png|.gif类型文件!"数据。<br />           unlink($upload_file);// 并删除这个文件<br />       }<br />   }else{<br />       $msg = '上传出错!';<br />   }<br />}

  代码处理流程:声明一个数组,保存着允许上传的文件类型–>获取文件名和文件临时存储路径–>截取文件名–>构造文件上传后的存储路径–>对文件进行转存–>比对白名单,如果存在就对文件进行重命名–>否则就删除文件。通过上面代码我们发现:

  服务器先通过move_uploaded_file函数把文件保存了,然后再去判断后缀名是否合法,合法就重命名,如果不合法再删除。重点在于,在多线程情况下,就有可能出现还没处理完,我们就访问了原文件,这样就会导致防护被绕过。

  我们上传一个文件上去,后端会检验上传文件是否和要求的文件是否一致。如果不能达到要求就会删除文件,如果达成要求就会保留,那么当我们上传文件上去的时候,检测是否到达要求需要一定的时间,这个时间可长可短,但是我们确确实实在某一刻文件已经上传到了指定地址,并且访问到这个文件。这时候就会造成条件竞争。

  看懂了源码以后,我们模拟一下条件竞争的场景:10000人同时上传文件1.php,另外有10000人在同时访问这个1.php;上传文件时,这个文件会有一段时间留存在服务器的上传目录下,而服务器脚本在进行判断文件是否合法而对文件进行删除时,会有一定的处理时间,可能在某个时间里,服务器还未来得及删除文件,从而导致我们对这个上传文件成功访问。

  实验过程

  1.首先,改变一下我们的一句话木马

  

  如果你想用一句话木马连接蚁剑的话那就太天真了,就是你运气好连接上蚁剑,也根本没时间查看就会被服务器删掉,所以我们就要猥琐一点,利用fputs()进行写入,当服务器解析解析完这个php文件的时候,我们就已经在该目录下创建了shell2.php了,而这个php文件里有一句话木马,且不是我们用户上传的,所以不会被删除.

  2.使用burpsuite进行抓包,拦截代理流量,并把拦截到的数据包发送到Intruder模块

  3.清空变量,并无止境地发送空包,模拟大量用户同时上传

  

  

  为了让服务器解析变得困难点,我们将线程提高到20

  

  设置完之后便可以开始爆破攻击了.以上便是模拟大量用户不停上传的场景,接下来我们要用模拟大量用户模拟访问.

  4.使用python脚本模拟用户访问上传目标地址

  import requests<br />url = "https://821-3a1ea0ce-39e7-4921-8b94-a2386d28c530.do-not-trust.hacking.run/upload/shell.php"<br />while True:<br />   html = requests.get(url)<br />   if html.status_code == 200:<br />       print("OK")<br />       break<br />   else:<br />       print("NO")

  我们可以随便上传一张图片,查看图片链接即可得到上传文件所在的url,将图片名改为我们上传的php文件,我这里是shell.php

  这个脚本的意思是不停访问这个网址,直到状态码返回200,即访问成功的意思.若出现OK,代表我们上传成功,即shell2.php也被写入了该目录之下,我们就可以用蚁剑连接了.(记得连接的是shell2.php而不是shell.php,因为shell.php只是为了写入shell2.php的一个工具而已)

  当然,如果用的是白名单,我们则需要使用图片马+条件竞争漏洞+文件包含漏洞三方一起才可以成功使用蚁剑连接.

  总结

  本次的讲解到这里基本就结束了,大家有空可以自己去过一些upload-labs这个靶场,肯定会有一些收获的.当然,文件上传漏洞可不仅仅上上面的这些,我列出来的都是基础,出的题肯定也是很灵活的,但是不管多灵活,只需要有足够扎实的基础以及代码审计能力,相信一定难不倒大家的.

  最后,会利用漏洞当然也需要知道相应的防御手段:

  不要暴露上传文件的位置

  禁用上传文件的执行权限

  黑白名单

  对上传的文件重命名,不易被猜测

  对文件内容进行二次渲染

  对上传的内容进行读取检查

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线