环境配置问题 phpStudy搭建,php版本需要选择ts的,相应的httpd-conf也需要调整,具体调整在下方给出;
所有配置都基于老版本的小皮,新版本干不起,估计和apache版本也有关系;
可以直接使用docker,又方便又省事;
上传思路 目的是为了把木马或webShell传到服务器上,服务器一般有判断,所以要绕过;
接下来的步骤思路即为靶场题目每道所得心得:
判断分为前端JS代码判断和后端代码判断,第一步 就是区分是前端还是后端:
使用抓包软件拦截状态时上传文件,如果抓不到但出结果了判断为前端,否则为后端;
前端可以由禁用JS方法来解决,后端的花样比较多,一般而言,第二步 先改包头content-type字段(其实大多数时候用不到,和ESP定律一个尿性);
第三步 区分黑白名单,黑名单就尝试后缀绕过,如php3,php5,phtml(此方法针对于过滤不完全的黑名单机制);
补充知识:apache服务的php版本中带有nts(not thread safe)的,是非多线程安全的,目前流通使用的大多都是TS的;
而往往nts版本的php会导致有些漏洞利用不了;
第四步 就是正儿八经的文件上传漏洞的入门内容了,.htaccess绕过,详细见P04;
.user.ini绕过,详见P05;
::$DATA绕过,详见P09;
第五步 便是正则绕过,各种特殊写法,在P05之后都有提及,(白名单)空字符(%00,0x00)截断;
第六步 是针对于文件内容检测的绕过思路,图片标识,图片🐎一类的尝试;
另类,则是条件竞争 ;
对于文件上传的总结位于P20,归于后缀绕过,内容绕过,条件利用三大类;
P01 通过BP抓包判断为前端检测,直接看前端JS,主要逻辑通过查找元素发现submit post之后返回一个check函数:
搜索找到这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function checkFile ( ) { var file = document .getElementsByName('upload_file' )[0 ].value; if (file == null || file == "" ) { alert("请选择要上传的文件!" ); return false ; } var allow_ext = ".jpg|.png|.gif" ; var ext_name = file.substring(file.lastIndexOf("." )); if (allow_ext.indexOf(ext_name) == -1 ) { var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name; alert(errMsg); return false ; } }
针对于前端检测而言,最有效的办法就是禁用网页的javaScript,这个禁用是针对全部的JS代码,所以有时候会影响一些功能导致无法使用,不过可以先试试;
这道题可以用上述方式解决;
P02 判断为后端执行检测;
这道题可以改content-type就可以绕过了:
P03 这道题提示上传php后为:不允许上传.asp,.aspx,.php,.jsp后缀文件!
这是文件黑名单,而目前大多数网址使用的文件上传服务都是白名单机制,而且非常严格;
后端php判断如下,这个判断一般是写在apache服务里的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $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 ); $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 . '文件夹不存在,请手工创建!' ; } }
其实它的过滤不完全,后缀只过滤掉了4个基础解析后缀,还有php3这种也能被解析成php文件的特殊文件后缀,俗称后缀绕过;
这道题如果是小皮环境,需要添加apache的httpd-conf内的php解析:
1 AddType application/x-httpd-php .php .php3 .php5 .phtml
还需要切换php版本为ts;
实在懒可以用BUUCTF的 (
P04 用之前的方法,会提示:此文件不允许上传!
查看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $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 ); $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 . '文件夹不存在,请手工创建!' ; } }
和P03一样的检测,只不过堵死了大部分后缀,而黑名单里的话,其中有两个后缀很有意思,一个是htaccess,一个是ini,这道题没有禁前者;
.htaccess 是一个服务器分布式配置文件,每个网址根目录都会有;
但相对于httpd.conf而言,httpd.conf是作用于全局,是apache的主要配置文件,影响整个服务器;
而.htaccess文件作用范围是局部的,常位于根目录和特定目录,只影响其所在的对应目录;
使用方法:.htaccess文件修改后即时生效,而Httpd.conf一般需要管理员级权限才能进行修改,修改需要重启apache服务器才能应用;
本题可以先上传一个.htaccess文件,里面配置这么一句话:
1 2 3 <FilesMatch "Hack"> SetHandler application/x-httpd-php </FilesMatch>
检测名字叫Hack的文件以php形式解析;
或者
1 AddType application/x-httpd-php .jpg .txt
意思是使jpg和txt后缀按照php文件的内容进行解析执行;
那么在这个上传目录内,传入的jpg和txt便会按照php执行了(要求服务端开启.htaccess功能 httpd.conf 所有override改为 all);
一般而言,直接改一句话木马的后缀为jpg,服务端很可能检测图片内容是否合法,所以可以使用命令合并一句话木马和一张图片来达成目的:
1 copy muma.php+tupian.jpg/b new.jpg
P05 前置知识
下面两个文件的关系和httpd.conf与.htaccess的关系类似,httpd.conf与.htaccess针对于apache服务器而言是有的;
而下面两个文件针对于php而言;
.user.ini 特定于用户和特定目录的配置文件,常常位于web应用程序的根目录下,用于覆盖或追加全局配置文件(php.ini)中的php配置选项;
作用范围:相对目录及其子目录;
生效:修改即生效;
注意,此文件生效前提 是php版本大于5.3.0,最好是7的版本 ,且Server API为 CGI/FastCGI ;
php.ini 存储对整个php环境生效的配置选项,常位于php安装目录中;
作用范围:所有运行在该php环境中的php请求;
生效方式:重启php或者服务器;
此关为.user.ini上传漏洞,利用前置要求:**.user.ini生效,且上传目录 已存在php文件**;
查看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $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 ); $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 . '文件夹不存在,请手工创建!' ; } }
一样的过滤方式,但没过滤.user.ini;
绕过写法:
1 Auto-prepend-file = file.txt
这个txt文件里只有php代码,当ini被加载后,这句话会使得这个目录下所有php文件自动包含 这个file.txt里的内容,再执行;
包含进去的代码被贴到已有php文件之后;
点加空格加点绕过 此题的另类绕过方法;
Windows 会将后缀名之后的.与空格自动删除;
这道题的绕过过程为:
获取文件名
删除文件末尾的点
以点分割为一个后缀名
将后缀名转为小写
对后缀名去多余空格
判断
当文件为file.php时,第三步获取到的文件后缀是.php,第一步获取的文件名为file.php;
但当文件为file.php. .时,第三步获取到的文件后缀是. ,第一步获取到的文件名为file.php. ;
所以可以绕过判断,并在上传后自动修正文件名为file.php;
P06 源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $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 ); $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 . '文件夹不存在,请手工创建!' ; } }
无法使用之前的两种绕过,但是它没判断大写,所以这道题可以大写绕过;
大写绕过 将后缀改为Php,PHP都可,只要不被匹配到;
P07 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $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 ); 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 . '文件夹不存在,请手工创建!' ; } }
这道题和之前的过滤并没有把首尾去空,可以利用空格绕过;
空格绕过 在匹配的时候,后缀字符串后面有一个空格不会被匹配到,但是传上去之后Windows会自动删除末尾的空格;
P08 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $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 ); $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 . '文件夹不存在,请手工创建!' ; } }
这道题并没有删除末尾点这一步;
加点绕过 如之前所说,在windows上后缀名之后的点和空格都会被删的原理;
P09 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $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 . '文件夹不存在,请手工创建!' ; } }
这次它的过滤比起之前的少了去除字符串::$DATA,那么这个东西是如何利用的呢?
额外数据流 在windows 操作系统中,文件名后面跟着::$DATA,表示一个文件附加数据流,数据流是一种用于在文件内部存储额外数据的机制;
正常情况下,文件只有一个默认数据流,通过文件名访问,但同时Windows NT文件系统支持在文件内部创建额外的数据流,存储其他信息用;
这些额外的数据流通过在文件后面添加::$DATA来访问;
写入方法:
利用重定向实现写入额外数据流;
1 2 echo "deadbeaf" >> file.png:Hacktype file.php >> file.png:Hack
上述后面的语句表示file.png文件的一个叫做Hack的额外数据流;
echo是将内容写入,type是将一个文件的内容写入;
查看方法:
此时会用记事本打开额外数据流的内容并显示;
::$DATA绕过 在php中,不会验证数据流后缀,如数据流名字为a.php,它只是一个数据流而不是一个文件,所以不会验证.php;
在上面也说了,一个文件后面跟着::$DATA就是一个数据流;
而windows中,文件名不允许冒号的存在,所以在上传时,改名文件后面跟着::$DATA,让检验部分认为这上传的是一个数据流而不是文件,从而绕过检测,在到达上传文件夹后,因windows的文件命名规则,将会删除冒号后面的东西变回文件,这就是绕过过程;
P10 与P05的另类绕过方式一样;
P11 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $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 . '文件夹不存在,请手工创建!' ; } }
这道题并不是用的in_array函数,而是str_ireplace函数,有什么区别呢?
前者会使用正则匹配整句话,而后者不去匹配整句,只会找这串字符(php)然后消除,即便后面加点加空格也会被消除,大写同理;
看起来不好绕了,因为特殊写法失效了,但其实这道题是最好绕的,sql注入里学的最有意思的便是双写绕过了;
因为只判断一次,所以直接后缀起名 pphphp ,匹配中中间的php使之剔除,留下php绕过;
P12 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $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类型文件!" ; } }
这道题与之前不同的是,使用了白名单机制;
可以知道php文件上传时,文件先是放于临时路径,之后转移到实际文件内,路径上的才是实际上的文件,之前改的文件名及其后缀只是包头内的一个字符串字段;
这道题的路径是可以被控制的,因为可以用Get传参;
此时可以用空字符截断,本身是Get部分加上后面的文件名和后缀内容组成一个文件路径,但可以在get部分直接写上一个完整文件路径,然后用空字符截断后面连接的部分达成绕过jpg的同时上传的文件类型是php;
在上传时,只需要设置参数即可成功:
1 ?save_path=../upload/file.php%00
P13 这道题和P12类似,get请求变为post请求,所以在后面添加0x00的hex编码即可;
P14 图片字节标识 魔术码
JPEG/JFIF 0xFF 0xD8
PNG 0x89 0x50
GIF 0x47 0x49
BMP 0x42 0x4D
TIFF 可变动,但也是前两个字节;
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function getReailFileType ($filename ) { $file = fopen($filename , "rb" ); $bin = fread($file , 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 ; } $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 = "上传出错!" ; } } }
这道题在检测上传文件的标识符,也就是前两个字节;
那么绕过思路则是在写好的一句话木马前面添加标识符进行绕过;
但这样还不够,服务器会把它按照图片解析,需要利用 文件包含漏洞 运行图片🐎中的木马;
文件包含前瞻 php设计之初为了使资源利用率更高效,设计了include这么一个东西;
当一个文件要引用一个另文件时,include进来就能直接使得这个文件调用 一次;
当操控者可以控制include后面跟着的文件路径时,漏洞就发生了,因为在包含之后的文件会以php解析的形式执行 ,当图片内有php木马时,include就导致了木马的执行;
在upload文件夹的上层,靶场自带了一个include.php文件,用来形成文件包含漏洞的:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php header("Content-Type:text/html;charset=utf-8" ); $file = $_GET ['file' ]; if (isset ($file )){ include $file ; } else { show_source(__file__ ); } ?>
那么此时上传之后,使用该漏洞便可成功绕过:
1 127.0.0.1/upload-labs-master/include.php?file=./upload/file.png
P15 这道题相对于P14对图片检测要求更严格,会检测上传图片的大小,此时就需要用到在P04说到的方法,copy图片和木马成为一个图片🐎;
之后利用文件包含漏洞绕过;
(实际上可以用010eiditor把木马以十六进制格式附加到图片末尾)
P16 同P15一样;
P17 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $filename = $_FILES ['upload_file' ]['name' ]; $filetype = $_FILES ['upload_file' ]['type' ]; $tmpname = $_FILES ['upload_file' ]['tmp_name' ]; $target_path =UPLOAD_PATH.'/' .basename($filename ); $fileext = substr(strrchr($filename ,"." ),1 ); 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 = "上传出错!" ; } }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的图片文件!" ; } }
这道题很明显针对了图片马,在上传之后将图片进行重写,也就是二次渲染;
如何判断图片被二次渲染?当使用P15,P16的方法不起作用时,上传图片另存为下载下来查看是否木马语句还存在即可判断;
如何绕过?要知道,二次渲染只是将图片的原始内容保存,其他内容进行重写,那么要找到原始的内容,也就是没改写部分的内容,进行对木马的插入即可;找寻重写部分只需要使用010进行diff比较即可;
理论来说GIF更容易,而修改PNG文件不能直接插入;
P18 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $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 = '上传出错!' ; } }
看起来和P12的源码很像,只不过PATH部分拼接由原来的GET传参变成了UPLOAD_PATH这么个东西,这说明没办法控制上传路径,就无法利用空字符截取了;
并且它是先move再in array,这和之前的顺序也有区别,这说明它是先将文件放到服务器的文件夹上,再去做后缀判断;
文件上传条件竞争 前提:文件先到服务器上,再做判断;
本质:抢夺线程的资源,使得上传的木马文件可以快速访问运行一次(上传之后,判断之前);
实际方式:一直上传,一直访问,在线程没反应过来的时候给与木马命令的执行;
总结:一个可执行php在目标文件夹一闪而逝即可利用;
但由于不可能只依靠这条一句话木马进行不可靠的信息传输,所以在需要这么访问的文件里,写入执行生成小马的语句,在它的上传文件目录中生成一个一直存在的一句话木马文件即可:
1 <?php fputs(fopen('shell.php' ,'w' ),'<?一句话木马?>' ); ?>
若有检测标识符,可以用base64编码绕过;
如何实现一直上传?一直访问?使用bp的测试器模块进行持续重放攻击,抓取上传php的包以及一直访问的包;
真是太裤辣!
P19 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ require_once ("./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); switch ($status_code ) { case 1 : $is_upload = true ; $img_path = $u ->cls_upload_dir . $u ->cls_file_rename_to; break ; case 2 : $msg = '文件已经被上传,但没有重命名。' ; break ; case -1 : $msg = '这个文件不能上传到服务器的临时文件存储目录。' ; break ; case -2 : $msg = '上传失败,上传目录不可写。' ; break ; case -3 : $msg = '上传失败,无法上传该类型文件。' ; break ; case -4 : $msg = '上传失败,上传的文件过大。' ; break ; case -5 : $msg = '上传失败,服务器已经存在相同名称文件。' ; break ; case -6 : $msg = '文件无法上传,文件不能复制到目标目录。' ; break ; default : $msg = '未知错误!' ; break ; } } class MyUpload {...... ...... ...... var $cls_arr_ext_accepted = array ( ".doc" , ".xls" , ".txt" , ".pdf" , ".gif" , ".jpg" , ".zip" , ".rar" , ".7z" ,".ppt" , ".html" , ".xml" , ".tiff" , ".jpeg" , ".png" ); ...... ...... ...... function upload ( $dir ) { $ret = $this ->isUploadedFile(); if ( $ret != 1 ){ return $this ->resultUpload( $ret ); } $ret = $this ->setDir( $dir ); if ( $ret != 1 ){ return $this ->resultUpload( $ret ); } $ret = $this ->checkExtension(); if ( $ret != 1 ){ return $this ->resultUpload( $ret ); } $ret = $this ->checkSize(); if ( $ret != 1 ){ return $this ->resultUpload( $ret ); } if ( $this ->cls_file_exists == 1 ){ $ret = $this ->checkFileExists(); if ( $ret != 1 ){ return $this ->resultUpload( $ret ); } } $ret = $this ->move(); if ( $ret != 1 ){ return $this ->resultUpload( $ret ); } if ( $this ->cls_rename_file == 1 ){ $ret = $this ->renameFile(); if ( $ret != 1 ){ return $this ->resultUpload( $ret ); } } return $this ->resultUpload( "SUCCESS" ); } ...... ...... ...... };
对于上传文件的代码审计:
先判断路径
再设置路径
再检查后缀以及文件大小
之后移动文件到服务器上
再进行随机重命名;
此时文件还没移动到服务器上就已经判断并删掉了,但是在白名单里面比之前多出来了一些压缩文件的后缀,不只是图片后缀了;
apache解析漏洞 对于apache服务器来说,访问一个服务器并不能解析的文件,它会对文件整体名字向前进行搜索,找到一个可以解析的后缀来执行;
例如一个文件叫做: file.php.7z ;
当用浏览器访问这个文件的时候,apache无法解析7z,就会把它当作一个php文件来执行;
而这道题虽然上传是在文件判断之后,但是7z后缀是可以上传上去的,只是之后会对其重命名,重命名变为xxx.7z,服务器不知道怎么解析,会用记事本打开;
赶在重命名之前对原文件进行访问,可以达到执行php的目的,实现针对于apache解析漏洞的条件竞争;
P20 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $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 = $_POST ['save_name' ]; $file_ext = pathinfo($file_name ,PATHINFO_EXTENSION); 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 . '文件夹不存在,请手工创建!' ; } }
又成黑名单绕过了;
可以自己取名文件名,判断用的是自己取的;
不多说了,大写就能绕过;
做个简单的总结吧;
黑名单绕过总结 黑名单一般考虑从后缀绕过下手,考虑特殊写法诸如php3一类的;
之后的一系列,.htaccess,.user.ini,::$DATA,正则匹配绕过;
白名单总结 这就要看条件了,如果能控制文件上传的路径,可以考虑空字符截断的后缀绕过;
如果能利用文件包含,可以考虑隐藏木马于可利用的文件上;
如果可执行文件在服务端能够一闪而逝,可以考虑使用条件竞争手段;
两类文件上传绕过都应该考虑对文件原始内容的检测,如检测关键字php,检测图片大小,二次渲染,做好正则绕过;
更多的,针对于以上这些漏洞,前提一定是php和apache的版本对应,版本不对,漏洞也就不复存在了;
P21 这道题按成一道审计题来做;
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $is_upload = false ;$msg = null ;if (!empty ($_FILES ['upload_file' ])){ $allow_type = array ('image/jpeg' ,'image/png' ,'image/gif' ); if (!in_array($_FILES ['upload_file' ]['type' ],$allow_type )){ $msg = "禁止上传该类型文件!" ; }else { $file = empty ($_POST ['save_name' ]) ? $_FILES ['upload_file' ]['name' ] : $_POST ['save_name' ]; if (!is_array($file )) { $file = explode('.' , strtolower($file )); } $ext = end($file ); $allow_suffix = array ('jpg' ,'png' ,'gif' ); if (!in_array($ext , $allow_suffix )) { $msg = "禁止上传该后缀文件!" ; }else { $file_name = reset($file ) . '.' . $file [count($file ) - 1 ]; $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH . '/' .$file_name ; if (move_uploaded_file($temp_file , $img_path )) { $msg = "文件上传成功!" ; $is_upload = true ; } else { $msg = "文件上传失败!" ; } } } }else { $msg = "请选择要上传的文件!" ; }
逻辑:判断content-type,之后判断文件后缀,使用白名单机制,无法利用条件竞争和图片马,所以这道题绝对是后缀绕过;
只是这是白名单,如何实现后缀绕过呢?
它这里将file打散为数组,以点分割,即为前面的内容以及后缀名,这就是漏洞所利用的地方,正则绕过的一种;
运行过程:
可以发现,在file这段部分是利用的重点,因为最后一个png是无法改动的;
第一想法是利用空字符截断,那么要解决的就一个问题,如何在第一个元素中塞进.这个符号;
但尝试之后是不现实的;
另一个思路,end函数和count函数,它明明可以在拼接的地方就用end,为什么非要炫技写count-1索引呢?
因为学C这种强规则性的语言学傻了,php的数组它不需要是连贯 的!
如令$file[0] = ‘name’, $file[7] = ‘gg’,count计算下来是2!!!
利用这个思路,让count-1去提取php就行了;
构造如下内容:
那么
$file[0] = ‘file’
$file[1] = ‘ggez’
$file[2] = null
$file[3] = ‘php’
$file[4] = ‘png’
end函数会提取png去比较,而count计算的时候只会计算出4,null不计入!
所以4-1=3,提取php进行最终的拼接,完成后缀绕过;
但对于有些php版本,还是会把$file[2]给计入count的计算,所以可以用bp抓包进行POST传递数组的方式:
总结一下吧,很多不同的漏洞呢,实际上都和php绕过有很大的关系,是相辅相成的;
代码审计的一个目的就是快速找到php代码中可以利用的部分,就如这道题的end和count一样,并没有很快的就发现这个利用点;
至此,文件上传漏洞及其labs结束;