漏洞重温之文件上传(FUZZ)
文件上传FUZZ思路通关upload-labs
Pass-16
黑盒阶段
进入第十六关,首先我们能看到,该页面的上传点为图片上传。
首先,先把对方想的简单一点,这里虽然是上传图片,但是可能只是前端js验证,我们只需要先将脚本后缀改为图片格式,然后抓包修改后缀,就有可能上传成功。
上传失败,可以得知,该网站对于上传文件的验证存在于后端。
从报错结果,我们可以得知,该位置采用的是白名单过滤,因为黑名单过滤一般不会告诉你该位置可以上传的文件类型。
既然得知该位置使用白名单对后缀进行过滤,那么我们可以使用的手段就只剩下00截断。
再由第二个图片的请求包信息,我们可以得知请求是以POST请求发送的。
绕过失败,该处无法通过00截断进行绕过。
一般情况下,在测试到该位置的时候,如果我们拿不到源码,黑盒测试就可以结束了。
白盒阶段
点击关卡查看源码属性,因为代码很长,就不截图了。代码如下。
$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的图片文件!";
}
}
这里,跟大家说一些可能会有帮助的点。
对于一些漏洞初学者来说,想要吃透一种类型的漏洞,在没有代码或者网络基础的情况下是很困难的事情,或者说至少在白盒情况下,是很难深入的发现一些问题的。
例如上面的代码,如果没有代码基础,想要理解代码的意思不是一个简单的事情。
但是,说到底,我们看代码只是为了知道我们的攻击为什么无法实现,或者说看对于该功能点,网站的防御如何,针对于这些防御,我们可以采用什么方法进行绕过。
例如上面的代码,我们虽然看不懂具体每行代码的含义,但是我们可以根据代码块上面的注释理解该位置的代码究竟发挥了什么作用。
以此为标准,我们可以看到,在代码开始,先是获取了上传文件的后缀,然后判断后缀是否合法,合法才进行上传。
然后,网站会使用上传的图片商城新的图片,并且给图片指定指定文件名。
从这个位置的代码,我们可以看到,其中一个判断流程的报错信息我们之前看到过。
也就是说,我们第二次采用00隔断进行上传的时候,其实绕过了第一层防御,因为给出的报错信息是在给图片进行二次渲染的地方的报错信息。
如果该网站并没有给图片进行二次渲染,可能我们的上传已经成功了。
但是,从此处,我们也可以了解到,哪怕我们该位置上传的是图片马,那么我们的图片马中插入的代码也会被二次渲染修改,导致无法正常使用。
既然我们已经清楚该位置上传失败是源于图片二次渲染,那么就有必要了解一下二次渲染是什么。
二次渲染:就是根据用户上传的图片,新生成一个图片,将原始图片删除,将新图片添加到数据库中。比如一些网站根据用户上传的头像生成大中小不同尺寸的图像。
这里,也就是说,我们上传的东西,会被网站作为样品再生成一个新的,并且将我们原本上传的文件删除。所以,想要绕过二次渲染,我们就需要知道,新生成的图片,跟我们原本的图片有什么不同,在相同处插入我们需要插入的代码或许就可以绕过。
首先,我们上传一个gif图片,因为图片的二次渲染,会利用我们的图片生成一个新图片,所以我们需要将上传的图片下载下来,通过16进制编辑器对比两张图片,可以发现,有一个地方是没有被修改的,所以我们只需要将php代码插入到没有被修改过的地方,就可以完成图片马的上传。
将代码写到蓝色范围之内,就可以获得一张可以绕过网站二次渲染的gif图片。
利用这个图片再次上传,获取路径。
利用文件包含访问该文件,检查我们的代码是否可以正常运行。
第十六关,通关。
当然,面对图片二次渲染,针对png和jpg两种格式,还有两种不同的应对方法,只是那两种我自己并没有成功,所以并没有在这里总结。
Pass-17
黑盒阶段
第十七关,可以看到,该处还是一个图片上传点,贯彻我们的思路,先把对方当成憨批,猜测他的防御只建立在前端。
跟第一关同样的思路,通过这里的报错信息,我们可以判断出,该位置采用白名单过滤。并且,请求包是POST请求,对付白名单过滤,我们可以采用00截断进行绕过。
可以看到,这里我们已经上传成功了,访问该文件。
第十七关,通关。
Pass-18
黑盒阶段
跟第十七关类似,上传点依然提示我们这里是一个图片上传点。针对上传,我的思路就是先把对面当憨批,跟xss一样,先输入一个测试马,成功了,那么对方的防御确实憨批,如果没成功,通过返回的报错,我们可以大致了解他们的防御情况。
可以看到,这里的报错信息和之前不同,猜测该位置网页采用的是黑名单过滤。
对于黑名单过滤,我们可以采用的方式就比白名单多了,简单来说,有以下几种:
1.上传特殊可解析后缀
2.配合解析漏洞
3.后缀大小写绕过
4.点绕过
5.空格绕过
6.::$DATA绕过
7.双后缀名绕过
8.00截断
因为这里我们并无法得知网站针对这些绕过方式做了什么防护,所以这里我们只能挨个尝试。
1.上传特殊可解析后缀
失败!
2.配合解析漏洞上传
失败!
3.后缀大小写绕过
失败!
4.点绕过
失败!
5.空格绕过
失败!
6.::$DATA绕过
失败!
7.双写后缀绕过
失败!
8.00截断
上传成功!
利用文件包含漏洞,测试文件里面代码是否可执行。
第十八关,通关。
Pass-19
黑盒阶段
进入十九关,可以看到,一个很明显的区别就是这里多了一个保存名称的选项,猜测,可能我们无论上传什么,都会被重命名为这个名称进行储存。所以我们可以尝试接着上传我们的测试图片马,然后将保存名称的后缀修改为php,尝试上传。
可以发现,在我们将保存文件的后缀修改之后,系统给出了报错,那么我们基本可以放弃绕过黑名单的几种方法了。
因为我们就算可以绕过网页的防御,保存的文件的名称依然是被修改掉了,所以在这上面花费的功夫基本等于无用功。
所以我们直接使用00过滤。
可以发现,文件已经上传成功。
访问文件,检查代码是否依然可执行。
第十九关,通关!
Pass-20
可以看到,在上传点下面也有一个保存名称框,跟之前一样,先测试一下是否可以通过直接修改保存名称的后缀来直接将我们上传的文件修改为php文件。
可以发现,在这里修改不行,同上一关的思路,我们没必要测试黑名单绕过方式,直接测试白名单绕过方式,也就是00截断。
可以发现,00截断失败了,一般在黑盒情况下,做到这里就可以结束了。
白盒阶段
查看源码,找到失败的原因。代码如下:
$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 = "请选择要上传的文件!";
}
可以发现
$file_name
经过reset($file) . '.' . $file[count($file) - 1];
处理。如果上传的是数组的话,会跳过
$file = explode('.', strtolower($file));
。并且后缀有白名单过滤$ext = end($file); $allow_suffix = array('jpg','png','gif');
而最终的文件名后缀取的是
$file[count($file) - 1]
,因此我们可以让$file
为数组。$file[0]
为smi1e.php/
,也就是reset($file)
,然后再令$file[2]
为白名单中的jpg。此时end($file)
等于jpg,$file[count($file) - 1]
为空。而$file_name = reset($file) . '.' . $file[count($file) - 1];
,也就是smi1e.php/.
,最终move_uploaded_file
会忽略掉/.
,最终上传smi1e.php
。
上面是我从博客里面看到的大佬的思路,大概就是说,因为正常情况下代码里面的有行代码无法绕过,所以在黑盒测试阶段我们的00阶段失效,但如果上传的是数组的话,就可以跳过这行代码。
{{uploading-image-428631.png(uploading...)}}
第二十关,未通关,需要再多研究。