因为我之前做过一遍靶场,但是并不是完全黑盒测试过的(查看提示+查看源码+wp),所以这次我准备再加上源码分析,顺便可以提升一下自己的php水平,所以下面的解析有可能会有些啰嗦,大家根据自身需求来进行选择,并且每关的通关方法并不是唯一的,大家仅供参考
当上传点使用了前端对后缀名验证的时候。
我们就可以使用burp suite 来绕过前端JS脚本
绕过以后我们就可以上传木马脚本 从而获取webshell
题目中出现的UPLOAD_PATH都是我们靶场文件夹下面的upload文件夹,如果没有请手工创建。
首先我们查看一下它的源码,这是一个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;
}
}
getElementsByName(name)方法可返回带有指定名称的对象的集合
。
getElementsByName(’ upload_file’)[0]因为是从elements中获取的元素值,所以后面的[0]代表数组的第一个元素,因为js的排序方法是以0为第一个
name是指元素的标签类似于id、class等
stringObject.indexOf(searchvalue,fromindex) 方法可返回某个指定的字符串值在字符串中首次出现的位置
stringObject.lastIndexOf(searchvalue,fromindex) 方法可返回一个指定的字符串值最后出现的位置
,在一个字符串中的指定位置从后向前搜索。
上面两个检索字符串的函数的两个参数的意思分别为:
searchvalue:必须。规定检索的字符串值
fromindex :可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索(如果是lastIndexOf()函数,则从字符串的最后一个字符开始检索)。
如果检索的字符串没有出现,那么返回-1
且该函数返回值为整数(检索字符出现的位置,0为起始)
首先作者定义了一个file变量用来获取name标签为’upload_file’数组的第一个元素的值,然后判断该变量如果为空则返回一个提示框。,又定义了一个用来获取上传文件类型的变量,它的值是文件名截取从点开始往后的字符串,也就相当于截取了后缀名。然后如果检索值等于-1(也就相当于上传了未定义的后缀名,没有检索到对应的字符串),那么将会报错,也就是实现了对我们上传文件的后缀名进行了验证
首先我们上传一张图片,我们发现上传成功,因为我们上传的是jpg文件,符合上传类型。
然后我们上传一个php脚本文件,发现上传失败,然后报错正是源码里面的errmsg的内容
因为我们的后缀名验证是js脚本,也就是前端,所以我们进行抓包,更改脚本内容,或者直接删除脚本,就可以成功进行前端验证绕过了。
我们首先拦截这次请求的响应,然后放包。
放包发现了,它进行了前端验证
接着我们只需要更改它的脚本内容或者直接删除该脚本即可进行绕过
增加.php后缀名
直接删除脚本
,然后即可进行绕过并且成功上传.php文件。
所谓Content-Type就是互联网媒体类型也就是MIME类型比如以下类型以及其它等等
HTML: text/html
JPEG: image/jpeg
GIF: image/gif
JS文档: application/javascript
我们看一下源码,这是一个php脚本
$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.'文件夹不存在,请手工创建!';
}
}
isset($var),用于检测变量是否设置,并且不是 NULL。如果该变量存在且非空则返回TRUE,否则返回FALSE
file_exists(path),检查文件或目录是否存在。如果指定的文件或目录存在则返回 true,否则返回 false。
move_uploaded_file(file,newlocation),将上传的文件移动到新位置.若成功,则返回 true,否则返回 false。
$_FILES,FILES 是一个预定义的数组,用来获取通过 POST 方法上传文件的相关信息。如果为单个文件上传,那么 $_FILES 为二维数组;如果为多个文件上传,那么 $_FILES 为三维数组。
$_FILES["file"]["name"] – 被上传文件的名称
$_FILES["file"]["type"] – 被上传文件的类型
$_FILES["file"]["size"] – 被上传文件的大小,以字节计
$_FILES["file"]["tmp_name"] – 存储在服务器的文件的临时副本的名称
$_FILES["file"]["error"] – 由文件上传导致的错误代码
参考自:https://www.cnblogs.com/summerGraden/p/12028765.html
首先作者先判断我们用POST方式提交(标签名为submit)的变量是否存在,如果存在,则接下来判断upload这个文件夹是否存在,如果存在则继续判断我们上传文件的文件类型,如果其文件类型为image/jpeg
或者为image/png
或者为image/gif
,那么让temp_file
这个变量获取存储在服务器中文件的临时名称,然后让img_path
这个变量为upload+/+被上传文件的名称。再进行判断如果move_uploaded_file()函数返回ture(也就是成功将文件移动到img_path路径下)那么is_upload
变量为true,即上传成功
我们首先上传一张正常的图片,发现可以上传,因为我们的图片是.jpg格式,其文件类型为image/jpeg,所以会成功上传
然后我们在上传php脚本文件,发现上传失败,因为上传的文件的文件类型并不是定义好的文件类型,所以无法进行上传
我们只需抓包,然后更改其content-type类型为:image/jpeg
即可绕过,即符合源码中对于Content-Type的限制,然后我们成功绕过了验证,完成了文件上传。
所谓黑名单就是限制了哪些不可以,除了不可以的都可以。所以我们只要构造黑名单之外的后缀名即可绕过
$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 . '文件夹不存在,请手工创建!';
}
}
trim(string,charlist) 函数移除字符串两侧的空白字符或其他预定义字符。
string:必需。规定要检查的字符串
charlist 可选。规定从字符串中删除哪些字符。如果省略,则移除以下所有字符
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格
deldot()函数,是作者自定义的函数,作用是删除文件名末尾的点,它在靶场文件夹下面的common.php文件中,下面是它的源码
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);
}
}
}
strrchr(string,char)函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。
string 必需。规定要搜索的字符串。
char 必需。规定要查找的字符。如果该参数是数字,
则搜索匹配此数字的 ASCII 值的字符。
例如strrchr(“abc.php”,’.’)那么回返回点及其以后的字符,那么返回值为.php
strtolower(string) 函数把字符串转换为小写。
str_ireplace(find,replace,string,count) 函数替换字符串中的一些字符(不区分大小写)
find 必需。规定要查找的值。
replace 必需。规定替换 find 中的值的值。
string 必需。规定被搜索的字符串。
count 可选。一个变量,对替换数进行计数。
in_array(search,array,type) 函数搜索数组中是否存在指定的值。如果在数组中找到值则返回 TRUE,否则返回 FALSE。
search 必需。规定要在数组搜索的值。
array 必需。规定要搜索的数组。
type 可选。如果设置该参数为 true,
则检查搜索的数据与数组的值的类型是否相同。
date(format,timestamp)函数把时间戳格式化为更易读的日期和时间。
format 必需。规定时间戳的格式。
timestamp 可选。规定时间戳。默认是当前时间和日期。
d - 表示月里的某天(01-31)
m - 表示月(01-12)
Y - 表示年(四位数)
1 - 表示周里的某天
H-带有首位零的 24 小时小时格式
h - 带有首位零的 12 小时小时格式
i - 带有首位零的分钟
s - 带有首位零的秒(00 -59)
a - 小写的午前和午后(am 或 pm)
rand(min,max) 函数返回随机整数。
min,max 可选。规定随机数产生的范围。
作者首先判断提交的变量是否存在,如果存在则接着判断保存路径是否存在,如果存在则定义了一个黑名单数组,里面是不能上传的文件类型,接着接收了上传文件的文件名,然后进行过滤文件名两端的预定义字符、删除文件名末尾的点、提取后缀名、转换为小写、删除特定字符串、继续对后缀名进行过滤预定义字符。
再进行判断如果提取出来的后缀名不在黑名单数组里面,那么进行文件移动的操作。
这关我们只需要构造黑名单以外的后缀名即可进行绕过,所以我们上传后缀名为.php1、.php2、phtml
等等都可以完成绕过,然后成功上传了一句话木马,用菜刀或者蚁剑连接即可
.htaccess文件,全称是超文本入口,提供了针对目录改变配置
的方法,即在一个特定的文档目录中放置一个包含一个或多个指令
的文件,以作用于此目录及其所有子目录。作为用户,所能使用
的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置
我们可以通过此文件,将我们的jpg文件全部解析为php文件,只需在此文件写入下面的代码
#将所有.jpg后缀的文件当作.php文件解析
AddType application/x-httpd-php .jpg
$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",".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 . '文件夹不存在,请手工创建!';
}
}
此函数与上一关函数完全相同,不做过多赘述
这关源码与上一关不同的就是它的黑名单数组几乎包括了我们能想到的后缀名,所以我们要用特殊的方法来进行绕过
只需要新建一个.htaccess
文件,包括我上面说到的代码,然后将.php文件的后缀名改为.jpg后缀名再进行访问,即可绕过。
此关相当于三四关的结合体,它不仅增加了黑名单的限制,而且还不让用
.htaccess文件绕过,所以这关我们要用后面的绕过方法来进行绕过
$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 . '文件夹不存在,请手工创建!';
}
}
与前面关卡的函数一样,这里就不做过多赘述了
是三四关的结合体,增加了黑名单长度,而且禁用.htaccess文件来进行绕过
我们可以用第十关的方法来进行绕过也就是点空格点绕过(. .),因为它的验证首先会删除文件名两端的空格,但是我们构造的后缀名的两端没有空格,所以此验证无效,接着它会删除我们后缀名中最后的一个点,然后又会过滤掉我们的一个空格,此时上传文件的后缀名为.php.
不在黑名单里面,所以可以成功被上传,而且又因为windows操作系统的原因,文件后缀名最后的点也会被删除,所以我们就完成了绕过。
windows 系统会忽略大小写。
而Linux系统默认对大小写敏感,如忽略则需要特殊配置
根据它对名字的过滤,正确写出没有被过滤的文件名字,即可上传
一个webshell
$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 . '文件夹不存在,请手工创建!';
}
}
此函数与上一关函数完全相同,不做过多赘述
此源码唯一与上一关不同的是,它少了对于文件名都要转换为小写的验证,所以我们可以通过大小写来进行绕过。
因为它的黑名单并没有.phP
的限制,所以我们可上传此类文件,然后在进行访问,因为windows对大小写不敏感,所以我们可以成功访问
Windows下xx.jpg[空格] 或者xx.jpg.这两类文件都是不允许存在的,
若这样命名,windows会默认去除空格或点
$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 . '文件夹不存在,请手工创建!';
}
}
本关出现的函数与前面相同,不做过多赘述。
这关源码唯一与前面不同的是,它没有trim()函数,也就是用来去除字符串两端的空格,所以我们如果在上传文件的后缀名里面加上空格,它在黑名单之外,我们就可以成功进行上传
Windows下xx.jpg[空格] 或者xx.jpg.这两类文件都是不允许存在的,
若这样命名,windows会默认去除空格或点
$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 . '文件夹不存在,请手工创建!';
}
}
本关出现的函数与前面相同,不做过多赘述。
此源码与前面不同的是,它没有deldot()函数,也就是不会删除我们文件名末尾的点(.),所以我们可以上传带有小数点的后缀名来进行绕过
我们直接上传后缀名为.php.
的文件,它后面的小数点不会被过滤,且也不在黑名单中,这样我们就可以成功进行上传。
利用条件: windows server + php
php在windows的时候如果文件名+"::$DATA"会把其之后的数据
当成文件流处理,不会检测后缀名。 且保持":: $DATA"之前的文件名
他的目的就是不检查后缀名…
$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 . '文件夹不存在,请手工创建!';
}
}
本关出现的函数与前面相同,不做过多赘述。
此关与前面不同的是,它没有对::$DATA进行过滤,所以我们可以用此来进行绕过,其次是此关在移动文件的时候只移动了文件名,并没有增加后缀名。
此关我们就是运用::$DATA
的效果来让脚本不检测后缀名
利用验证规则不完善导致我们可以使用文件名.的方式进行绕过.
$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 . '文件夹不存在,请手工创建!';
}
}
本关出现的函数与前面相同,不做过多赘述。
此源码与第五关的一致,我们直接可以用点空格点绕过
此题就不在多进行赘述,详情可以看第五关通过方法
waf 常用手段
服务端逻辑对不合法的后缀名进行了替换为空
那我们可以扩充下思路
例如后端限制了php,如果发现我们上传的文件名后缀为php 就替换为空
a.php -> .php -> a
a.pphphp ->php -> a.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","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 . '文件夹不存在,请手工创建!';
}
}
本关出现的函数与前面相同,不做过多赘述。
此关与前几关不同的是,作者这次直接把包含在黑名单内的所有后缀名都替换成了空,所以我们可以想到用双写来进行绕过。
我们只需双写php来进行绕过即可,即上传文件后缀名为.pphphp
的文件即可进行绕过
PHP <= 5.3.4
php.ini magic_quotes_gpc = Off
利用手动添加字符串标识符的方式来将后面的内容进行截断
,而后面的内容又可以帮助我们绕过检测
在GET请求直接加
在POST请求需要改二进制
$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类型文件!";
}
}
substr(string,start,length)函数返回字符串的一部分。
string 必需。规定要返回其中一部分的字符串。
start 必需。规定在字符串的何处开始。
正数 - 在字符串的指定位置开始
负数 - 在从字符串结尾开始的指定位置开始
0 - 在字符串中的第一个字符处开始
length 可选。规定被返回字符串的长度。默认是直到字符串的结尾。
正数 - 从 start 参数所在的位置返回的长度
负数 - 从字符串末端返回的长度
strrpos(string,find,start) 函数查找字符串在另一字符串中最后一次出现的位置。
返回字符串在另一字符串中最后一次出现的位置,如果没有找到字符串则返回 FALSE。
注释: 字符串位置从 0 开始,不是从 1 开始。
string 必需。规定被搜索的字符串。
find 必需。规定要查找的字符。
start 可选。规定在何处开始搜索。
此源码不同于前面的是,作者这次设置了白名单,除了白名单之内所有后缀名都不能进行上传。
作者首先提取了文件的后缀名(从获得到名字的字符串里面,从中检索点最后一次出现的位置来进行截取后缀名)
然后进行移动文件,但是此关移动文件的路径是由GET方式得到的,所以是可控的
我们可以运用%00来进行截断,且直接将想要上传的文件拼接到save_path
变量中。
首先我们上传的是一个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 = $_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类型文件!";
}
}
此关函数和上一关一模一样,不做过多赘述。
此关唯一与上一关不同的就是,这次的拼接路径是有POST来接收的
和上一关相同,需要注意的点就是,因为POST不会对%00进行解码,所以我们有两种方法
二是更改其十六进制进制码。
和上一关一样,上传图片的原地址为
删除掉多余的内容,就可以成功进行访问了
图片马制作方法:
copy a.jpg /b + shell.php /a shell.jpg
/b 表示一个二进制文件
+ 表示将多个文件合并成一个文件
/a 表示一个ASCII文本文件
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 = "上传出错!";
}
}
}
fopen(filename,mode,include_path,context) 函数打开文件或者 URL。
filename 必需。规定要打开的文件或 URL。
mode 必需。规定要求到该文件/流的访问类型。可能的值见下表。
include_path 可选。如果也需要在 include_path 中检索文件的话,
可以将该参数设 为 1 或 TRUE。
context 可选。规定文件句柄的环境。Context 是可以修改流的行为的一套选项。
"r" 只读方式打开,将文件指针指向文件头。
"rb" 以二进制方式打开
"r+" 读写方式打开,将文件指针指向文件头。
"w" 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
"w+" 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
"a" 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
"a+" 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
"x" 创建并以写入方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用 失败并返回 FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。
这和给底层的 open(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的。
此选项被 PHP 4.3.2 以及以后的版本所支持,仅能用于本地文件。
"x+" 创建并以读写方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用失败并返回 FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。
这和给底层的 open(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的。
此选项被 PHP 4.3.2 以及以后的版本所支持,仅能用于本地文件。
fread(file,length) 函数读取文件(可安全用于二进制文件)。
file 必需。规定要读取打开文件。
length 必需。规定要读取的最大字节数。
fclose() 函数关闭一个打开文件
unpack(format,data) 函数从二进制字符串对数据进行解包。
format 必需。规定在解包数据时所使用的格式。
data 可选。规定被解包的二进制数据。
format 参数的可能值:
a - NUL-padded string
A - SPACE-padded string
h - Hex string, low nibble first
H - Hex string, high nibble first
c - signed char
C - unsigned char
s - signed short (always 16 bit, machine byte order)
S - unsigned short (always 16 bit, machine byte order)
n - unsigned short (always 16 bit, big endian byte order)
v - unsigned short (always 16 bit, little endian byte order)
i - signed integer (machine dependent size and byte order)
I - unsigned integer (machine dependent size and byte order)
l - signed long (always 32 bit, machine byte order)
L - unsigned long (always 32 bit, machine byte order)
N - unsigned long (always 32 bit, big endian byte order)
V - unsigned long (always 32 bit, little endian byte order)
f - float (machine dependent size and representation)
d - double (machine dependent size and representation)
x - NUL byte
X - Back up one byte
@ - NUL-fill to absolute position
intval(mixed $var [, int $base = 10 ]) 函数用于获取变量的整数值。
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
此题源码比较复杂,因为牵扯到进制的问题,我们可以理解为,作者会通过二进制码,来判断上传的文件格式,如果不是jpg、png、gif的话会无法上传。
综上我们的结题方法就是,制作图片马,直接进行上传即可。
然后我们上传我们的图片马,然后运用题目要求我们的文件包含漏洞,进行访问php代码即可成功绕过。
和上一关类似都是上传图片马
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 = "上传出错!";
}
}
}
image_type_to_extension(int $imagetype [, bool $include_dot = TRUE) — 根据指定的图像类型返回对应的后缀名。
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");
作者首先用getimagesize() 函数来获取图片的各种信息然后,运用索引2也就是图片的类型,作为后缀名变量,我们仍然可以上传图片马来进行绕过
通过方法和上一关一样,只是这关判断图片类型的方法不同于上一关。
首先我们要打开php扩展
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 ( string $filename ) 用来读取一个图像的第一个字节并检查其签名。
如果发现了恰当的签名则返回一个对应的常量,否则返回 FALSE。返回值和 getimagesize() 返回的数组中的索引 2 的值是一样的,但本函数快得多。
值 常量
1 IMAGETYPE_GIF
2 IMAGETYPE_JPEG
3 IMAGETYPE_PNG
4 IMAGETYPE_SWF
5 IMAGETYPE_PSD
6 IMAGETYPE_BMP
7 IMAGETYPE_TIFF_II(Intel 字节顺序)
8 IMAGETYPE_TIFF_MM(Motorola 字节顺序)
9 IMAGETYPE_JPC
10 IMAGETYPE_JP2
11 IMAGETYPE_JPX
12 IMAGETYPE_JB2
13 IMAGETYPE_SWC
14 IMAGETYPE_IFF
15 IMAGETYPE_WBMP
16 IMAGETYPE_XBM
此关与上一关不同的是,此关用的是php内置函数来判断图片的类型,从而进行白名单验证。
这关同上一关一样,只需要上传一个图片马即可进行绕过,这关只是与上关判断文件类型的方式不同
二次渲染是指服务器把我们上传的图片内容信息提取,然后
重新组合
我们可以获取到重新生成的图片,然后和我们的图片马进行对比
看他从新生成后的和我们上传的原始文件的差异,进而再次构造图片马
$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的图片文件!";
}
}
basename(path,suffix) 函数返回路径中的文件名部分。
path 必需。规定要检查的路径。
suffix 可选。规定文件扩展名。如果文件有 suffix,则不会输出这个扩展名。
imagecreatefromjpeg(string $filename) — 由文件或 URL 创建一个新图象。
成功后返回图象资源,失败后返回 FALSE 。
filename JPEG 图像的路径。
unlink(filename,context) 函数删除文件。
若成功,则返回 true,失败则返回 false。
filename 必需。规定要删除的文件。
context 可选。规定文件句柄的环境。Context 是可修改流的行为的一套选项。
srand(seed) 函数播下随机数发生器种子。
seed 可选。用 seed 播下随机数发生器种子。
strval() — 获取变量的字符串值。
imagejpeg(resource $image [, string $filename [, int $quality ]]) — 输出图象到浏览器或文件。
image
由图象创建函数(例如imagecreatetruecolor())返回的图象资源。
filename
文件保存的路径,如果未设置或为 NULL,将会直接输出原始图象流。
如果要省略这个参数而提供 quality 参数,使用NULL。
quality
quality 为可选项,范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。默认为 IJG 默认的质量值(大约 75)。
imagepng()、imagegif()函数类似与imagejpeg()函数,这里就不做过多赘述了
此关作者就是,把我们上传的图片在服务器进行重新渲染,那么我们如果单独上传一个图片马上去,我们的图片经过二次渲染后,里面的一句话木马就会消失,所以我们要对比两张图片的差异,从而再次构造图片马
我们首先上传一张gif图片马【我这里推荐的是用windows自带的画图工具,然后保存为gif格式】(因为上传其它图片马二次渲染会稍微麻烦一点具体可参考此文章https://xz.aliyun.com/t/2657#toc-13
)到到服务器,然后下载服务器给我们重新进行渲染过的图片,再放到winhex或者Hxd对比其差异。
首先打开上传的gif图片马
然后再打开从服务器下载的作者帮我们二次渲染过后的gif图片马
然后我们首先看一下这两张图片的最后
第一张是我们自己上传的图片马
第二张是二次渲染过后的图片马
我们发现被重新渲染过后的图片马,没有了我们增加上去的php脚本了,所以我们要对比两张图片相同的地方,然后将我们的脚本重新写上去,然后保存继续上传
条件竞争漏洞是一种服务器端漏洞,由于服务器端在处理用户请求时,
是并发进行的,因此 如果并非处理不当或相关操作逻辑顺序设计的不合理
时,将会导致此类问题的发生。
$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 = '上传出错!';
}
}
rename(oldname,newname,context) 函数重命名文件或目录。
若成功,则该函数返回 true。若失败,则返回 false。
oldname 必需。规定要重命名的文件或目录。
newname 必需。规定文件或目录的新名称。
context 可选。规定文件句柄的环境。context 是可修改流的行为的一套选项。
本关作者的想法就是,如果你上传的文件后缀名不在白名单里面,他会直接删除你上传的文件。
这关我们的解题步骤就是,在文件上传后删除前访问文件,就会出现你上传的文件还没来得及删除就已经被访问了。这样也达到了我们的目的
这关我们要用到burpsuite的爆破模块
一、我们首先上传一个脚本文件,并且把它发送到爆破模块
这个脚本文件的内容为
fputs(fopen('shell.php','w'),'');?>
然后payload我们选择没有负载,因为我们只是想多次上传
二、我们访问我们上传的脚本,且同样发送到爆破模块
同样设置无负载
然后我们一起开始进行攻击,如果我们运气好,一次攻击就可以访问到
//index.php
$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;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
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 flag to check if the file exists is set to 1
if( $this->cls_file_exists == 1 ){
$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );
}
......
......
......
};
本次函数都在Pass-19下面的mydownload.php中
此关作者首先判断了我们的文件后缀名,然后是文件大小、移动文件、最后是重命名
这关作者应该是想让我们,运用条件竞争+Apache解析漏洞来进行上传的,我没有试出来正确的方法,所以这关我是直接用的上传图片马+文件包含漏洞进行绕过的。
首先我们做好图片马,然后进行上传,发现直接上传成功
然后我们运用作者提供给我们的文件包含漏洞,成功进行绕过
$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(path,options) 函数以数组的形式返回文件路径的信息。
path 必需。规定要检查的路径。
process_sections 可选。规定要返回的数组元素。默认是 all。
可能的值:
PATHINFO_DIRNAME - 只返回 dirname(所在路径)
PATHINFO_BASENAME - 只返回 basename(文件名)
PATHINFO_EXTENSION - 只返回 extension(后缀名)
源码类似与第12、13关只不过是,这关作者是用POST方法来对我们的文件名进行接收,同时拼接到路径上去,那么这就和我们第13关类似,只不过是13关是由POST接收的路径名,而这关只接收了文件名。但是两关所要考察我们的内容应该是一样的
我们首先上传一个php脚本,然后在save_name文件名的后面添加%00(注意要URL解码,或者更改16进制码
)
然后我们就成功上传了,这是访问的原地址
我们只需要删除多余的内容即可访问
$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 = "请选择要上传的文件!";
}
explode(separator,string,limit) 函数把字符串打散为数组。
separator 必需。规定在哪里分割字符串。
string 必需。要分割的字符串。
limit 可选。规定所返回的数组元素的数目。
可能的值:
大于 0 - 返回包含最多 limit 个元素的数组
小于 0 - 返回包含除了最后的 -limit 个元素以外的所有元素的数组
0 - 返回包含一个元素的数组
strtolower(string) 函数把字符串转换为小写。
empty() 函数用于检查一个变量是否为空。
empty() 判断一个变量是否被认为是空的。当一个变量并不存在,或者它的值等同于 FALSE,那么它会被认为不存在。如果变量不存在的话,empty()并不会产生警告。
end() 函数将数组内部指针指向最后一个元素,并返回该元素的值(如果成功)。
reset() 函数将内部指针指向数组中的第一个元素,并输出。
count(array,mode) 函数返回数组中元素的数目。
array 必需。规定数组。
mode 可选。规定模式。可能的值:
0 - 默认。不对多维数组中的所有元素进行计数
1 - 递归地计数数组中元素的数目(计算多维数组中的所有元素)
作者首先检查了我们上传文件的MIME类型,然后定义了允许的上传类型,然后判断我们上传文件的类型,如果我们上传的文件类型在白名单里面,然后判断我们由POST接收的文件名是否为空,如果为空,那么我们上传文件就会保存为原来的文件名,如果不为空,那么我们上传的文件名会保存为,我们POST接收的文件名。然后作者判断了我们的file
变量如果不是数组,那么我们将file
变量先转换为小写然后再用explode打成数组(以点为分隔符),然后取它的后缀名,并且定义了一个允许上传的后缀名数组,然后判断如果我们取的后缀名在白名单里面,然后取到了我们的文件名,然后进行移动
结合我上面对源码的讲解,其实这关我们需要注意几个点,一个是如果上传的是php文件,那么记得要改MIME类型,这样第一层我们突破了,二我们需要上传一个数组,否则我们只要上传后缀名为php的文件,就会被拦截,所以我们需要抓包更改我们上传的文件为数组,然后在路径那块,运用%00截断
我们首先上传一个php文件
然后根据我上述所说,一步一步进行更改即可绕过(记得%00要解码
)
这是原地址,我们只要删除多余的内容即可绕过
因为文件上传靶场我是第一次做,前言也有讲到,我做的过程不是很顺利,所以这次wp难免会有纰漏,如果大家发现了请私聊我。
至于为啥非要写这么一篇网上几乎都有的内容呢,我本着自己可以当做笔记,且分析源码,也能增加我的php知识,慢慢一点积累,到最后一定会成功的。
借用周董《蜗牛》里面的一句歌词,“我要一步一步往上爬,等待阳光静静看着它的脸,小小的天有大大的梦想,重重的壳裹着轻轻地仰望”
本文的函数分析参考自:
https://www.w3school.com.cn/
https://www.runoob.com/