点击进入第二十关,并选择显示代码:
$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 = "请选择要上传的文件!";
}
- empty()用于判断是否为空值
- a?b:c 是条件表达式,如果a为真,那么表达式b;如果a为假,那么表达式c
- explode() 用于将字符串拆分为不同的字符串。该函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
- end() 用于返回数组的最后一个元素
- reset()用于将数组的内部指针回退到第一个元素,并返回第一个数组元素的值;如果数组为空,则返回FALSE
- count() 用于返回数组的长度
对代码进行分析,可以看到首先进行mime检测,判断是否是图片类型。然后使用条件表达式给file变量赋值,如果如果存在 $_POST['save_name']) 的值,那么把他赋值给file,否则将 $_FILES['upload_file']['name'] 的值赋给file变量:
然后判断file是不是数组,如果file的值不是数组,使用explode()函数将其拆分为文件名和后缀组成的数组,并将数组的最后一个值作为后缀名。
接着判断后缀名是否是jpg png gif中的一种,如果满足条件执行下面的代码为上传的文件进行命名:
$file_name = reset($file) . '.' . $file[count($file) - 1];
我们发现在给上传文件命名的时候,使用 $file[count($file) - 1] 确定文件的后缀。同时根据上面代码,我们可以将file的传入值设置为数组。我们首先确认是否存在$_POST["save_name"]的值。(确认file的值来自于谁,以方便我们后续修改)。首先我们先在文本文档中写一段一句话木马,然后将其另存为jpg格式。
然后打开bp进行抓包工具并开启监听状态,然后点击上传刚刚制作的jpg格式的木马文件。我们发现请求头中存在 save_name ,根据上面的条件表达式判断语句,也就是说file的值来源于$_POST["save_name"]。而且我们还知道file的值可以为数组,那么我们此时可以直接构建一个数组。
那么数组该怎么构建呢?见下图:
我们发现通过白名单验证时的后缀名和文件命名时的后缀名来源不一样。既然使用数组的最后一项用作白名单验证,那么我们就使得数组的最后一项设置为 jpg(png 或 gif都可),而数组的第一项肯定要用作存储文件名。即数组的大致结果如下:
以数组的的第n项作为后缀名,假如数组只有两项,即file = [文件名,"jpg"],则count[$file]=2(因为数组下标从0开始,所以需要减一)
那么 reset[$file] = 文件名,$file[1] = "jpg"。
这里我们可以发现,$file后缀名的获取和$file的长度息息相关,当长度确定,那么后缀名的下标也就确定了。
那么我们可以利用这一点进行绕过白名单,从而上传我们的php文件,我们令
$file[0] = "myupload.php"
$file[2] = "jpg"
这里我们不对 $filep1]的值作定义。这时数组的长度仍然为2,但$file[1]不存在。
根据代码:
file_name = “myupload.php” + "." + $file[1]
因为$file[1]不存在,那么file_name的最终结果为: "myupload.php. " 。这时我们就成功上传了一个php文件。
理论证明结束,实践继续,对刚刚我们抓取的包进行修改,即修改为 $file[0] = "myupload.php" , $file[2] = "jpg" 的数组
添加一段 Content-Disposition 内容,修改save_name为数组形式,并赋值:
然后点击forward发送请求,这时我们发现文件上传成功
右键单击复制文件地址,并在浏览器访问,发现获取服务器php信息成功
上一关(网络安全 文件上传漏洞-19 第十九关 Pass-19)
下一关(网络安全 文件上传漏洞-21 第二十一关 Pass-21)