文件上传代码分析以及修复

靶场1

源码分析:
此下为后端源码
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "clientcheck.php"){
   $ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR =  "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR.'inc/uploadfunction.php';

$html='';
if(isset($_POST['submit'])){//判断是否存在变量submit
//     var_dump($_FILES);
   $save_path='uploads';//指定在当前目录建立一个目录
   $upload=upload_client('uploadfile',$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>";
  }
}


?>

upload_client-->调用以下函数表

function upload_client($key,$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(!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;

}

以上代码并没有对传入的文件的文件名后缀做过滤

而在代码的前端

<script>
   function checkFileExt(filename)
  {
       var flag = false; //状态 定义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>

经过代码分析,此处为前端校验,但是在后端并不存在任何校验。

代码修复:

#在后端添加校验

分析靶场2

可以发现其为修改其文件名后缀就可以进行上传文件

下面进行分析源码

以下为文件上传的核心代码

if(isset($_POST['submit'])){
//     var_dump($_FILES);
   $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>";
  }
}
#调用了以下函数
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;
   
}
$_FILES[$key]['type']这个表示为文件名的后缀

根据上述代码可以分析出不能只看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)){//通过截取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;
  }

靶场三的核心代码主要是根据判断mime,和getimagesize()获取图片的属性信息来判断上传是否合法

此三类靶场都可以加入以下代码进行防御

 $file=$_FILES[$key]['name']
$file=substr(strrchr($file, '.'), 1);
if(!in_array($file,$wenjian)){
var_dump($_FILES[$key]['name']);
  $return_data['error']='上传的图片只能是sssss格式的!';
  $return_data['return']=false;
      return $return_data;
      }

通过截取文件名的后缀与我们预先定义的文件名进行对比,这样采用白名单机制进行验证

接下来,我们尝试对我们修复后文件上传,再一次尝试攻击

1.尝试多写几个filename

 

 

 

无法尝试绕过

posted @ 2021-12-29 13:04  安全课-信仰  阅读(294)  评论(0编辑  收藏  举报