1.不安全的文件上传漏洞概述
文件上传功能在web应用系统很常见,比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后,后台会对上传的文件进行判断 比如是否是指定的类型、后缀名、大小等等,然后将其按照设计的格式进行重命名后存储在指定的目录。 如果说后台对上传的文件没有进行任何的安全判断或者判断条件不够严谨,则攻击着可能会上传一些恶意的文件,比如一句话木马,从而导致后台服务器被webshell。
所以,在设计文件上传功能时,一定要对传进来的文件进行严格的安全考虑。比如:
--验证文件类型、后缀名、大小;
--验证文件的上传方式;
--对文件进行一定复杂的重命名;
--不要暴露文件上传后的路径;
--等等...
2.client check
我们试试上传phpninfo文件,命名为1.php
提示说不符合文件要求
看提示说只让上传图片文件
我们把1.php后缀改为.png,开始上传并抓包,再send to repeater
再把文件名改回成1.php,点send,php文件上传成功,且暴露了路径
访问:http://127.0.0.1/pk/vul/unsafeupload/uploads/1.php
成功!
3.MIME type
看提示说还是只允许上传图片
但我们不听他的,照样上传1.php,提示又说只能是jpg,jpeg,png形式的
那我们继续把1.php后缀改为.png,开始上传并抓包,再send to repeater
后面的过程就和第一题一样了,也能成功访问:http://127.0.0.1/pk/vul/unsafeupload/uploads/1.php
那么不同之处在哪呢
查看本关的防护源代码
$mime是一个包含合法MIME类型的数组,也就是MIME类型白名单;然后将这个白名单作为参数传入了upload_sick()函数进行服务器端的检测。
只有上传文件的MIME类型包含在预定义的MIME白名单之中时,才允许上传,即只允许上传'image/jpg'、'image/jpeg'、'image/png'这三种MIME类型的文件
upload_sick()函数的定义如下,该函数不安全之处在于两点:
(1)仅检查了MIME类型,可以通过抓包修改绕过。
(2)保存文件的时候没有重命名文件,这样即使网页不回显文件保存路径,也有很大概率可以被攻击者猜测到。
整个关卡还有一处不安全,就是和第一关一样,同样回显了文件保存路径。
成功上传webshell,并且知道文件保存路径后,攻击者就可以连接shell了。
4.getimagesize
这关就有所不同了,尝试前两关的方法,我们上传1.png,结果如下
发现这次我们的伎俩被识破了。。。。。
试试上传一个真的jpeg文件,结果如下,可见后端代码修改了文件名,但是还把文件保存路径回显了出来
getimagesize看起来像是个函数,用搜索引擎搜索,结果如下:
getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。
getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。
查看源码:
getimagesize()文件中截取的关键代码片段,可以发现通过调用upload()函数来实现getimagesize()检测功能,其中upload()函数在外部文件uploadfunction.php文件中定义,并通过include_once来引入。
接下来,再分析具体实现文件类型检测功能的upload()函数,定位到uploadfunction.php文件,截取其中关于upload()函数的定义代码,upload()定义了5个参数,分别为$key,$size,$type,$mime,$save_path,其中$key取值为“uploadfile”,代表form表单中<input>的name属性、$size限制上传文件大小、$type代表允许上传的文件类型白名单,$mime代表允许MIME类型白名单、$save_path代表文件上传后的存储路径
可见,在Pikachu靶场getimagesize检测时,分别依次检测文件大小、检测文件类型、检测文件MIME类型3中方式来实现检测,加强文件上传过滤效果,且在进行文件保存的时候以随机数为文件名进行保存,因此在绕过的时候需要同时绕过这三个检测手段
//进行了严格的验证
function upload($key,$size,$type=array(),$mime=array(),$save_path){
$arr_errors=array(
1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
3=>'文件只有部分被上传',
4=>'没有文件被上传',
6=>'找不到临时文件夹',
7=>'文件写入失败'
);
// var_dump($_FILES);
if(!isset($_FILES[$key]['error'])){
$return_data['error']='请选择上传文件!';
$return_data['return']=false;
return $return_data;
}
if ($_FILES[$key]['error']!=0) {
$return_data['error']=$arr_errors[$_FILES[$key]['error']];
$return_data['return']=false;
return $return_data;
}
//验证上传方式
if(!is_uploaded_file($_FILES[$key]['tmp_name'])){
$return_data['error']='您上传的文件不是通过 HTTP POST方式上传的!';
$return_data['return']=false;
return $return_data;
}
//获取后缀名,如果不存在后缀名,则将变量设置为空
$arr_filename=pathinfo($_FILES[$key]['name']);
if(!isset($arr_filename['extension'])){
$arr_filename['extension']='';
}
//先验证后缀名
if(!in_array(strtolower($arr_filename['extension']),$type)){//转换成小写,在比较
$return_data['error']='上传文件的后缀名不能为空,且必须是'.implode(',',$type).'中的一个';
$return_data['return']=false;
return $return_data;
}
//验证MIME类型,MIME类型可以被绕过
if(!in_array($_FILES[$key]['type'], $mime)){
$return_data['error']='你上传的是个假图片,不要欺骗我xxx!';
$return_data['return']=false;
return $return_data;
}
//通过getimagesize来读取图片的属性,从而判断是不是真实的图片,还是可以被绕过的
if(!getimagesize($_FILES[$key]['tmp_name'])){
$return_data['error']='你上传的是个假图片,不要欺骗我!';
$return_data['return']=false;
return $return_data;
}
//验证大小
if($_FILES[$key]['size']>$size){
$return_data['error']='上传文件的大小不能超过'.$size.'byte(500kb)';
$return_data['return']=false;
return $return_data;
}
//把上传的文件给他搞一个新的路径存起来
if(!file_exists($save_path)){
if(!mkdir($save_path,0777,true)){
$return_data['error']='上传文件保存目录创建失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
}
//生成一个新的文件名,并将新的文件名和之前获取的扩展名合起来,形成文件名称
$new_filename=str_replace('.','',uniqid(mt_rand(100000,999999),true));
if($arr_filename['extension']!=''){
$arr_filename['extension']=strtolower($arr_filename['extension']);//小写保存
$new_filename.=".{$arr_filename['extension']}";
}
//将tmp目录里面的文件拷贝到指定目录下并使用新的名称
$save_path=rtrim($save_path,'/').'/';
if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$new_filename)){
$return_data['error']='临时文件移动失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
//如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去)
$return_data['save_path']=$save_path.$new_filename;
$return_data['filename']=$new_filename;
$return_data['return']=true;
return $return_data;
}
此时后端检测手段丰富,具有很强的过滤效果,无法直接上传php脚本文件。
那么,怎么绕过getimagesize ( )检测呢?
那便是制作图片木马
首先,我们准备php脚本,我们还用phpinfo,因为比一句话木马安全嘛
再准备一张图片
win+r输入cmd,进入命令提示符,先进入桌面,再输入命令dir,展示桌面的文件。
输入命令copy /b 666.png + 1.php 123.png,在桌面生成123.png的图片马。
发现桌面上已经有该图片木马了
再回到题目上传该图片,发现上传成功!
确认一下文件上传到的路径和文件名称与回显信息是否一致