0


Pikachu靶场01-文件上传漏洞详解

说明:为了更好的理解,需要掌握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即可。

标签: 安全 前端 web安全

本文转载自: https://blog.csdn.net/li20132273/article/details/128154014
版权归原作者 AkangBoy 所有, 如有侵权,请联系我们删除。

“Pikachu靶场01-文件上传漏洞详解”的评论:

还没有评论