说明:为了更好的理解,需要掌握PHP $_FILES函数以及PHP文件包含相关基础知识
第1章 前端检测
1.1 前端检测代码
** 1、上传文件表单**
以下代码片段是前端上传文件的form表单,分析可以发现在<input>标签中通过onchange调用了JavaScript函数checkFileExt(),借助该函数实现对文件类型的检测。
<form class="upload" method="post" enctype="multipart/form-data" action="">
<input class="uploadfile" type="file" name="uploadfile" onchange="checkFileExt(this.value)"/><br />
<input class="sub" type="submit" name="submit" value="开始上传" />
</form>
** 2、前端检测代码**
以下代码片段是前端JavaScript检测代码,首先通过函数取出上传文件的后缀名,然后去匹配文件类型白名单,只有jpg、png、gif三种图片类型的文件才允许上传,否则返回“上传的文件不符合要求,请重新选择!”
<script>
function checkFileExt(filename)
{
var flag = false; //状态
var arr = ["jpg","png","gif"];
//取出上传文件的扩展名
var index = filename.lastIndexOf(".");
var ext = filename.substr(index+1);
//比较
for(var i=0;i<arr.length;i++)
{
if(ext == arr[i])
{
flag = true; //一旦找到合适的,立即退出循环
break;
}
}
//条件判断
if(!flag)
{
alert("上传的文件不符合要求,请重新选择!");
location.reload(true);
}
}
</script>
1.2 前端检测绕过
1、关闭浏览器JavaScript解析器
FireFox地址栏输about:config-->找到javascript.enabled并将其关闭(默认开启),关闭后便可直接上传php文件
**2、BurpSuite抓包修改文件类型 **
** 首先,**将待上传的php文件后缀修改为图片格式,并在前端页面加载修改后的文件,如下:
其次,浏览器开启代理,并使用BurpSuite抓包,将图片格式重新修改为php格式,从而绕过浏览器JavaScript检测,如下:
第2章 后端检测绕过
2.1 后端MIME类型检测
Pikachu靶场提供了两个服务端检测的页面,一个是MIME类型检测,另一个是getimagesize()。我们首先来看看MIME类型检测。如下所示实现MIME检测的文件是servercheck.php,找到这个文件进行代码分析。
以下是从servercheck.php文件中截取的关键代码片段,可以发现通过调用upload_sick()函数来实现MIME检测功能,其中upload_sick()函数在外部文件uploadfunction.php文件中定义,并通过include_once来引入
<?php
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'inc/uploadfunction.php';
$html='';
if(isset($_POST['submit'])){
$mime=array('image/jpg','image/jpeg','image/png'); //指定MIME类型,这里只是对MIME类型做了判断。
$save_path='uploads'; //指定在当前目录建立一个目录
$upload=upload_sick('uploadfile',$mime,$save_path); //调用函数
if($upload['return']){
$html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['new_path']}</p>";
}else{
$html.="<p class=notice>{$upload['error']}</p>";
}
}
?>
定位到uploadfunction.php文件,截取其中关于upload_sick()函数的定义代码,upload_sick()定义了三个参数,分别为$key,$mime,$save_path,其中$key取值为“uploadfile”,代表form表单中<input>的name属性、$mime是一个数组,代表允许MIME类型白名单、$save_path代表文件上传后的存储路径。
//只通过MIME类型验证了一下图片类型,其他的无验证,upsafe_upload_check.php
function upload_sick($key,$mime,$save_path){
$arr_errors=array(
1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
3=>'文件只有部分被上传',
4=>'没有文件被上传',
6=>'找不到临时文件夹',
7=>'文件写入失败'
);
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;
}
//验证一下MIME类型
if(!in_array($_FILES[$key]['type'], $mime)){
$return_data['error']='上传的图片只能是jpg,jpeg,png格式的!';
$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;
}
}
$save_path=rtrim($save_path,'/').'/';//给路径加个斜杠
if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$_FILES[$key]['name'])){
$return_data['error']='临时文件移动失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
//如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去)
$return_data['new_path']=$save_path.$_FILES[$key]['name'];
$return_data['return']=true;
return $return_data;
}
在以上代码中,实现MIME类型检测的代码如下,这段代码含义是:只有上传文件的MIME类型包含在预定义的MIME白名单之中时,才允许上传,即只允许上传'image/jpg'、'image/jpeg'、'image/png'这三种MIME类型的文件
//验证一下MIME类型
if(!in_array($_FILES[$key]['type'], $mime)){
$return_data['error']='上传的图片只能是jpg,jpeg,png格式的!';
$return_data['return']=false;
return $return_data;
}
2.2 绕过后端MIME类型检测
当后端检测机制是MIME类型检测时,由于后端是通过HTTP请求头中Content-Type字段获取的,所以一般借助BurpSuite抓包,直接修改HTTP请求中Content-Type类型为图片类型即可,如下:
2.3 后端getimagesize()检测
在了解完MIME类型检测后,接下来看看getimagesize()检测。如下所示实现getimagesize()检测的文件是geimagesize.php,找到这个文件进行代码分析。
以下是从getimagesize()文件中截取的关键代码片段,可以发现通过调用upload()函数来实现getimagesize()检测功能,其中upload()函数在外部文件uploadfunction.php文件中定义,并通过include_once来引入
<?php
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'inc/uploadfunction.php';
$html='';
if(isset($_POST['submit'])){
$type=array('jpg','jpeg','png'); //指定类型
$mime=array('image/jpg','image/jpeg','image/png');
$save_path='uploads'.date('/Y/m/d/'); //根据当天日期生成一个文件夹
$upload=upload('uploadfile','512000',$type,$mime,$save_path); //调用函数
if($upload['return']){
$html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['save_path']}</p>";
}else{
$html.="<p class=notice>{$upload['error']}</p>";
}
}
?>
接下来,我们分析具体实现文件类型检测功能的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=>'文件写入失败'
);
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;
}
2.4 绕过后端getimagesize()检测
此处后端检测手段丰富,具有很强的过滤效果,无法直接上传php脚本文件,但是可以通过构建图片马的方式来获取shell
首先,制作图片马,将恶意脚本文件写入图片之中:
echo "<?php phpinfo();?>" >> eval.png
其次,加载图片马,启动浏览器代理,上传文件:
其次,BurpSuite抓包增加文件幻数,绕过getimagesize()检测
最后,结合文件包含漏洞包含901422638a19e4a4edc568521343.png来getshell即可。
版权归原作者 AkangBoy 所有, 如有侵权,请联系我们删除。