文件上传练习靶场–upload-labs通关记录以及对文件上传漏洞的总结
Pass-01 直接上传php文件,出现弹框提示上传失败
尝试抓包,但是因为弹框未抓到上传文件的包,所以猜测是前端JS代码对文件进行了检测,直接查看网页源代码,发现检测JS代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <script type ="text/javascript" > 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 ; } } </script >
代码大致流程是对比文件名的最后一个后缀是否是jpg,png,gif,如果不是则前端拦截文件,上传失败。
既然是前端进行,我们只要绕过前端,再利用抓包修改文件名后缀,即可成功上传PHP文件。我们先上传一个后缀名为JPG,内容为PHP代码的文件1cmd.jpg ,再通过抓包修改文件名为1cmd.php ,过程如下图所示
Pass-02 直接上传PHP文件,提示文件类型错误,猜测后台代码对文件类型进行了检测,抓包修改文件类型为image/jpeg ,如下图所示
本关检测代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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.'文件夹不存在,请手工创建!' ; } }
Pass-03 上传PHP文件,提示禁止不允许上传.asp,.aspx,.php,.jsp后缀文件 ,尝试修改文件名为.jpg.php ,修改文件类型,大写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 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 . '文件夹不存在,请手工创建!' ; } }
过滤了后缀名为.asp,.aspx,.php,.jsp的文件,但是没有过滤phtml 文件
上传成功http://127.0.0.1/upload-labs/upload/201903031949124726.phtml
另外修改后缀名为php3 也可以
Pass-04 上传php,phtml,php3等文件都失败,过滤代码如下:
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 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.'/' .$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } } ?>
黑名单几乎过滤掉了所有问题后缀名,但是唯独没有过滤.htaccess 文件,我们上传一个.htaccess 文件,内容为:
1 SetHandler application/x-httpd-php
上传之后,该路径下所有文件都会被解析成PHP格式文件,我们再上传包含PHP代码的图片文件
访问http://127.0.0.1/upload-labs/upload/4cmd.jpg
Pass-05 跟上一关区别的是黑名单又增加了.htaccess文件,过滤代码如下:
1 2 3 4 5 6 7 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);
但是仔细观察发现这关并没有将上传文件的后缀名通过strtolower 进行大小写转化的处理,所以很简单,上传一个.PHP 文件即可
Pass-06 1 2 3 4 5 6 7 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);
黑名单一样,并对文件名进行小写转化处理,但是未对文件名通过trim 函数进行去空处理,所以对后缀名进行加空,即可上传成功
Pass-07 1 2 3 4 5 6 7 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);
黑名单相同,对文件名进行去空和小写转换处理,但是没有通过自定义的deldot 函数进行末尾去点处理,所以上传后缀名为.php. 文件,windows特性上传后会自动将后缀名的点去掉
Pass-08 1 2 3 4 5 6 7 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 = trim($file_ext);
这关没有通过str_ireplace函数去除字符串::$DATA ,在文件名后缀加上::$DATA 即可绕过
Pass-9 1 2 3 4 5 6 7 8 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);
相对于前面几关而言,这关过滤的较为完善,可以看到,过滤的流程为:(1)文件名去空(2)文件名去点(3)截取最后一个点后的字符串(4)将截取的文件后缀转换为小写(4)将截取的文件名后缀去除字符串::$DATA(5)将截取的文件名后缀去空
我们可以看一下deldot函数的具体代码:
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 ); } } }
可以发现,检测流程是从文件名的最后一位开始检测,是点就去掉末位,继续向前检测,只要检测到文件名最后一位不是点,就返回过滤后的文件名,而且去点只有一次
针对上述过滤流程,我们可以构造后缀名为.php. .(点+空格+点) ,经过去点过滤后的文件名为.php. (点+空格) ,之后截取文件名后缀自然就绕过检测,上传的文件名最后后缀为.php.(点)
Pass-10 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 . '文件夹不存在,请手工创建!' ; } }
这关是将上传文件的文件名通过str_ireplace 函数去除黑名单中的文件后缀,但是这个函数的缺点是只能去除一次,所以双写就能绕过,上传文件名后缀为.pphphp
Pass-11 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (isset ($_POST['submit' ])){ $ext_arr = array ('jpg' ,'png' ,'gif' ); $file_ext = substr($_FILES['upload_file' ]['name' ],strrpos($_FILES['upload_file' ]['name' ],"." )+1 ); if (in_array($file_ext,$ext_arr)){ $temp_file = $_FILES['upload_file' ]['tmp_name' ]; $img_path = $_GET['save_path' ]."/" .rand(10 , 99 ).date("YmdHis" )."." .$file_ext; if (move_uploaded_file($temp_file,$img_path)){ $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = "只允许上传.jpg|.png|.gif类型文件!" ; } }
这关开始采用了白名单的形式,要求上传文件名后缀名必须为jpg,png,gif,但是我们可以发现上传文件的路径是通过GET方式传递的参数save_path 进行拼接的,所以在save_path 末尾利用%00 截断绕过
Pass-12 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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类型文件!" ; } }
这关拼接的参数save_path 是通过POST方式传递的,同样抓包修改save_path ,但是因为POST不像GET能URL解码%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 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; }
通过读取文件的前两个字节来判断文件类型,本关的目的是上传图片马,所以利用copy 命令将图片文件和php文件进行合并成图片马文件,命令如下:
1 copy 1.jpg/b + 13cmd.php/a 13cmd.jpg
最后通过带有文件包含漏洞的文件检测图片马
Pass-14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ; } }
利用getimagesize 函数获取文件类型是否是图片文件,跟上一关一样,可以用copy 命令生成图片马,也可以在文件内容的开头加入GIF89A 伪装成GIF 文件
Pass-15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ; } }
同样利用copy 命令生成图片马或者在文件内容开头加入GIF89A 即可上传图片马
Pass-16 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 = "上传出错!" ;
这关规定了文件的后缀名必须是jpg,png或gif,文件类型Content-Type 必须为image/jpeg,image/png或image/gif,而且上传后还经过imagecreatefromgif 函数进行图片二次渲染的过程,我们可以先试着上传一个利用copy 命令生成的图片马
可以看到成功上传,接下来访问上传的图片马
可以看出上传的图片马末尾的PHP代码经过二次渲染后发生了变化
二次渲染后的图片是会有部分内容不会发生变化的,我们可以试着上传一张完整的GIF 图片,对比上传后的图片与原来的图片
我们可以发现开头部分内容前后是没有变化的,那么我们就在开头部分直接添加PHP代码再上传
成功上传,再利用文件包含漏洞访问一下上传的图片马
这个方法只适合gif图片,如果是png和jpg方法较为麻烦,具体可以参考https://xz.aliyun.com/t/2657
Pass-17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 = '上传出错!' ; } }
这关先经过move_uploaded_file 函数进行文件上传,再利用白名单过滤文件,如果不是图片文件再通过unlink 函数将文件删除,我们可以利用条件竞争的原理,利用多线程不断上传php文件,再后台还未来得及通过unlink 函数删除php文件时,访问到webshell
发包的同时在浏览器不断访问17cmd.php文件
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 25 26 27 28 29 30 31 32 33 34 35 36 37 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 ; } }
这关同样使用了白名单的形式规定了合法的后缀名,上传后再通过rename 函数重命名。我们可以观察这关的白名单中存在压缩包的后缀名
1 2 3 var $cls_arr_ext_accepted = array( ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt", ".html", ".xml", ".tiff", ".jpeg", ".png" );
那么跟上一关一样,我们可以利用条件竞争,通过多线程发送上传后缀名为.php.7z 的文件的包,当服务器还未来得及将文件改名时访问上传的webshell
可以看到有的响应包的提示是文件还来不及被重命名
在浏览器中访问18cmd.php.7z
成功访问webshell
Pass-19 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 . '文件夹不存在,请手工创建!' ; } }
这关以一个POST方式传递的参数save_name 作为上传文件保存的文件名,同时通过pathinfo 函数对文件名的后缀名进行黑名单检测,但是我们可以发现,并没有对该参数进行一系列过滤处理(去点,去空,去::$DATA字符串,大小写转化)
我们先测试一下pathinfo 函数:
1 2 3 4 5 echo pathinfo("cmd.php" ,PATHINFO_EXTENSION); echo pathinfo("cmd.php." ,PATHINFO_EXTENSION); echo pathinfo('cmd.php::$DATA' ,PATHINFO_EXTENSION); echo pathinfo("cmd.php " ,PATHINFO_EXTENSION);echo "<br>" ; echo pathinfo("cmd.PHP" ,PATHINFO_EXTENSION);echo "<br>" ;
通过测试说明,一系列之前关卡的绕过方法都是可以的
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 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 = "文件上传失败!" ; } } } }
这关首先检查了上传的文件类型,然后将POST方式传递的参数save_name (如果为空,则上传的文件名)作为文件名变量$file ,对$file 进行了是否是数组的判断,如果不是数组则以“.” 为分界符打散成数组,并取出数组最后一个元素(通过end 函数)作为文件名后缀进行白名单的检测,通过检测的话就取出数组的第一个元素(通过reset 函数)与$file[count($file) - 1] 拼接成最终的文件名上传
如果变量$file 作为字符串,则我们只能上传图片马,但如果作为数组 ,则不需要经过explode 函数的处理,那么我们就考虑对$file 数组赋值如下:
1 2 3 4 $file = array (); $file[1 ] = "20cmd" ; $file[2 ] = "php" ; $file[3 ] = "jpg" ;
那么被检测的后缀名变量$ext 和最后上传的文件名变量$file_name 的值如下:
1 2 $ext = end($file) == "jpg" $file_name = reset($file) . '.' . $file[count($file) - 1 ] == "20cmd.php"
上传的payload如下图所示
还可以考虑通过利用%00 截断函数move_uploaded_file ,对$file 数组赋值如下:
1 2 3 $file = array (); $file[0 ] = "20cmd.php " ; $file[1 ] = "jpg" ;
上传的payload如下图所示
文件上传漏洞总结 文件上传的检查主要分为两大部分:客户端 检查和服务器端 检查
一.客户端检查 客户端主要是通过前端的JS 代码进行检查,如果只是单纯的前端检查,我们只需要按照前端的检查标准发送请求包,再通过抓包修改请求包的内容,如第一关,抓包修改一下文件名后缀再提交即可成功上传webshell
二.服务器端检查 服务器端则是通过后台脚本代码(本靶场为PHP)进行检查,检查主要分为三部分:检查Content-type ,检查后缀名 ,检查文件内容
1.检查Content-type 抓包修改Content-type字段为合法内容即可
2.检查后缀名 检查后缀名分为黑名单 检测和白名单 检测
2.1黑名单检测 列举出一系列禁止上传的文件后缀名进行过滤,常用的绕过方法有以下几种:
(1)上传特殊可解析后缀 :如phtml ,php3 ,php5 ,pht
(2)上传.htaccess 文件:内容为SetHandler application/x-httpd-php ,上传的所有文件都会被当做php文件进行解析,前提是需要服务器相关配置开启
(3)大小写绕过:在Linux没有特殊配置的情况下,这种情况只有win可以,因为win会忽略大小写,例如Pass-05中未使用strtolower 函数进行小写转化处理,那么将后缀名改成PHP 即可上传成功
(4)空格,点绕过:Win下xx.php [空格] 或xx.php. 这两类文件都是不允许存在的,若这样命名,windows会默认除去空格或点 ,例如Pass-06和Pass-07未使用trim 函数或者自定义的deldot 函数进行去空和去点处理,就可以利用该方法进行绕过上传
(5)::$DATA 绕过:NTFS文件系统包括对备用数据流的支持。这不是众所周知的功能,主要包括提供与Macintosh文件系统中的文件的兼容性。备用数据流允许文件包含多个数据流。每个文件至少有一个数据流。在Windows中,此默认数据流称为:$ DATA,例如Pass-08中,未使用str_ireplace 函数去除::$DATA ,那么上传后缀名为.php::$DATA 即可上传
(6)双写后缀名绕过:当服务器利用函数(如Pass-10中使用str_ireplace 函数)将敏感的后缀名替换为空时,双写后缀名,如.pphphp 即可绕过
(7)上传.7z 压缩包绕过:.7z 是一种压缩包文件的格式,我们上传cmd.php.7z 文件,再访问该文件时能够正常访问到php页面,这属于Apache解析漏洞,Apache解析文件时,如果后缀名不认识,则会继续想前解析,会解析到php,这就是Apache的解析漏洞
2.2白名单检测 列举出只允许上传的文件后缀名,过滤掉不属于白名单中的文件,常用的绕过方法有以下几种:
(1)MIME绕过:检查http包的Content-type 字段来判断文件类型,直接修改该字段值即可
(2)%00截断:利用%00 截断move_uploaded_file 函数,只解析%00 前的字符,%00 后的字符不解析,通常运用在GET方式,因为GET方式传入能自动进行URL解码,如Pass-11
(3)0x00截断:原理同%00截断,只不过是通过POST方式传递参数,需要通过Burp在十六进制形式中修改
3.检查文件内容 通过一些检查文件内容的函数进行判断是否是图片格式的文件,可以大致分为对文件头检查,getimagesize 函数检查,exif_imagetype 函数检查和二次渲染,通常我们只能够上传图片马,然后结合文件包含漏洞进行getshell,常用的绕过方法有以下几种: (1)利用copy 命令生成图片马:命令具体为copy 1.jpg/b + cmd.php/a shell.jpg
,生成图片马后上传成功,但是同时还得存在文件包含漏洞才能执行图片马
(2)利用GIF89A 伪造成GIF文件:在PHP文件开头内容加入GIF89A ,服务器通过getimagesize 会认为这是GIF文件
(3)绕过二次渲染:上传PNG和JPG图片马方法较为复杂,但是GIF图片马只需要找到上传前后两个文件经过二次渲染未改变内容的地方,并在其中添加PHP代码即可
三.代码逻辑 这一类属于比较特别的,根据服务器端代码执行的逻辑通过条件竞争 上传黑名单文件,条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的,因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生 。
以Pass-17为例,程序先进行文件上传后再判断文件是否合法,不合法再进行删除,如果利用多线程持续发送上传PHP文件的请求包,并不断访问上传的文件,服务器会来不及将不合法文件删除,我们也能因此而成功执行PHP文件代码
最后,再附上一张别人的总结图