文件上传的漏洞介绍及常见防御方法V1.0

内容不完善,持续补充中......

文件上传漏洞原理

在文件上传的功能处,若服务端脚本语言未对上传的文件进行严格验证和过滤,导致恶意用户上传恶意的脚本文件时,就有可能获取执行服务端命令的能力,这就是文件上传漏洞。

文件上传漏洞触发点

相册、头像上传、视频、照片分享、附件上传(论坛发帖、邮箱)、文件管理器等。

文件上传漏洞的形成条件

  • 文件能够通过前端和后端过滤和文件处理

  • 文件内容不会改变,能够被正确存储

  • 存储位置在Web容器控制范围内

  • 攻击者有权限访问存储目录并有权执行文件

只要破坏了其中的任一条件即可防止文件上传漏洞。


文件上传漏洞常见防御方法

0.无防护

以下代码实现了一个简单的文件上传功能

index.html

<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="uploadfile" >
  <input type="submit">
</form>

上传文件的后端处理,基于Spring Boot

package com.example.fileuploadvul.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Controller
public class UploadFile {
    @PostMapping("/upload")
    public String uploadFile(@RequestParam("uploadfile")MultipartFile file){
//获取文件名
        String filename = file.getOriginalFilename();
        //文件保存路径
        String path="F:\\tmp\\";
        File outfile = new File(path + filename);
        try {
            file.transferTo(outfile);
        }catch (IOException e){
            e.printStackTrace();
        }
        return "success";
    }
}

上面的代码没有任何的防护功能,存在文件上传漏洞。用户可以随意的上传任何文件、木马。


1.前端进行js校验

增加攻击成本,该种方式容易被绕过。

javascript示例:

<form action="/upload" method="post"  onsubmit="return judge()" enctype="multipart/form-data">
    <input type="file" name="uploadfile" id="checkfile" >
    <input type="submit" value="提交">
    <p id="msg"></p>
</form>
<script type="text/javascript">

    function judge(){
        var file=document.getElementById("checkfile").value;
        if (file==null||file==""){
            alert("请选择要上传的文件");
            // location.reload(true);
            return false;
        }
        var isnext=false;
        //定义允许上传的文件类型
        var filetypes=[".jpg",".png"];
        //提取上传文件的类型,其中这里需要注意用lastIndexOf而非indexOf,否则会被1.php.php绕过
        var fileend=file.substring(file.lastIndexOf("."));
        //判断上传文件类型是否允许上传写法一
        for (var i=0;i<filetypes.length;i++){
            if (filetypes[i]==fileend){
                isnext=true;
                break;
            }
        }
        if (!isnext){
            document.getElementById("msg").innerHTML="文件类型不允许";
            // location.reload(true);
            return false;
        }else {
            return true;
        }
        //判断上传文件类型是否允许上传写法二
        if (fileend.indexOf(filetypes) == -1) {
            var errMsg = "该文件不允许上传";
            alert(errMsg);
            return false;
        }
    }
</script>

绕过方法:

这里限制了只能上传.jpg .png文件,但是攻击者可以用burpsuite拦截修改包进行绕过。

如图,上传一个内容为php的jpg文件,然后在此处将filename修改为1.php,即可绕过前端js验证。

另外也可以利用插件禁用js后进行提交。


2.白名单/黑名单

2.1黑名单

2.1.1java示例:

@Controller
public class UploadFile {
    @PostMapping("/upload")
    public String uploadFile(@RequestParam("uploadfile")MultipartFile file, Model model){
        boolean flag=true;
        String filename = file.getOriginalFilename();
        System.out.println(filename);
        String suffix=filename.substring(filename.lastIndexOf("."));
        String[] blacklist={".jsp",".php",".exe",".dll","vxd","html"};//后缀名黑名单
        for (String s : blacklist) {
            if (suffix.equals(s)){
                flag=false;
                break;
            }
        }
        if (flag){
            String path="src\\main\\resources\\static\\upload";
            File fileDir = new File(path);
            File outfile = new File(fileDir.getAbsolutePath()+File.separator + filename);
            try {
                file.transferTo(outfile);
                return "success";
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        else {
            model.addAttribute("msg","非法文件类型");
        }
        return "index";
    }
}

2.1.2php示例:

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array('.asp','.aspx','.php','.jsp');
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //收尾去空

        if(!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;            
            if (move_uploaded_file($temp_file,$img_path)) {
                 $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

2.1.3绕过方法:

如图成功上传了phps文件。

黑名单容易出现大小写(pHp)、特殊可解析后缀(pht)、.htaccess、点、空格、::DATA、双写等绕过问题。

另外可能使用Intruder模块进行枚举后缀名,如使用字典

另外,“判断文件后缀名是否存在黑名单中的字符,将对应的字符串替换为空”这种方式也是不可取的。

String[] blacklist={"jsp","php","exe","dll","vxd","html"};//后缀名黑名单
for (String s : blacklist) {
    if (suffix.indexOf(s)!=-1){
        suffix=suffix.replace(s,"");//后缀存在黑名单字符串,则将字符串替换为空
    }
}

这种方式可以通过双写 phphpp 进行绕过。

2.2白名单

2.2.1java示例:

String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
String[] white_suffix = {"gif","jpg","jpeg","png"};
Boolean fsFlag = false;
for (String suffix:white_suffix){
    if (contentType.equalsIgnoreCase(fileSuffix)){
        fsFlag = true;
        break;
    }
}
if (!fsFlag){
    return "suffix not allow";
}

2.2.2php示例:

$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类型文件!";
    }
}

对后缀进行白名单限制不易被绕过。

因此建议使用白名单而非黑名单。


3.检查文件类型MIME Type

3.1java示例

//1、MIME检测
    String contentType = file.getContentType();
    String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"};
    Boolean ctFlag = false;
    for (String suffix:white_type){
        if (contentType.equalsIgnoreCase(suffix)){
            ctFlag = true;
            break;
        }
    }
    if (!ctFlag){
        return "content-type not allow";
    }

3.2php示例

if (($_FILES['upload_file']['type'] == 'image/jpeg') || 
($_FILES['upload_file']['type'] == 'image/png') || 
($_FILES['upload_file']['type'] == 'image/gif'))

增加攻击成本,该种方式容易被绕过。

3.3绕过方法:

如图可以直接修改包。


4.使用随机数改写文件名

4.1php示例

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;

4.2java示例

String uuid = UUID.randomUUID().toString();
fileName = uuid+fileName.substring(fileName.lastIndexOf("."));

能够增加攻击成本,另外可以防范某些特殊名称文件(shell.php.rar.rar)


5.对文件内容进行校验

5.1对文件头进行校验

.jpg    FF D8 FF E0 00 10 4A 46 49 46
.gif    47 49 46 38 39 61
.png    89 50 4E 47

以图片为例,可以截取文件的头部几个字节进行校验,如使用getimagesize(不建议使用)

增加攻击成本,易被绕过,攻击者同样可以在文件的头部增加如下字节:

5.2对文件内容进行检测

绕过<?:

<script language='php'>@eval($_POST[cmd]);</script>

绕过php:

<?= @eval($_POST['cmd']);?>

同时绕过:

<script language='pHp'>@eval($_POST[cmd]);</script>

6.对文件的处理逻辑。

若先将文件放入路径,再去检验文件合法性并删除,会存在条件竞争漏洞。

6.1示例:

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 = '上传出错!';
    }
}

先用move_uploaded_file 将上传的文件移动到上传目录,再判断后缀名并就删除文件。

我们多线程异步上传文件A并不断地访问自己上传的文件A,可以文件A进行删除之前,成功执行文件A,

若文件A代码为:

');?>

则执行成功可以生成稳定的shell.php

解决方案:经过充分完整的检查之后再上传。


7.禁止上传文件被执行

7.1存储服务器

将存储上传文件的位置设计在另一台只具备存储功能的文件服务器或数据库上,与Web应用服务器分开,这样即使木马被上传进来,也因为文件服务器不能执行脚本而没有办法实施攻击。

7.2设置权限

改存储上传文件的目录的执行脚本的权限。如linux系统下使用chmod命令修改目录的rwx权限。

7.3修改配置

修改.htaccess、apache的httpd.conf配置文件、Nginx增加配置

7.3.1 .htaccess

<FilesMatch "\.(?i:php|php3|php4|php5)">
Order  allow,deny
Deny  from  all
</FilesMatch>

7.3.2 修改apache的配置文件httpd.conf

<Directory D:\wwwroot\public\uploads>
<FilesMatch "\.(?i:php|php3|php4|php5)">
    Order  allow,deny
    Deny  from  all
</FilesMatch>
</Directory>

7.4 隐藏上传文件路径

示例:使用blob把网站上的全部图片链接加密。

后台返回图片:

protected void Page_Load(object sender, EventArgs e)
    {
        string url = Server.MapPath("~/Images/abg.jpg");
        Bitmap b = new Bitmap(url);
        MemoryStream ms = new MemoryStream();
        b.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        Response.ClearContent();
        Response.ContentType = "image/Jpg";
        Response.BinaryWrite(ms.ToArray());
    }

JavaScript代码:

    <img id="img1" src="">
    <script type="text/javascript">
        //创建XMLHttpRequest对象
        var xhr = new XMLHttpRequest();
        //配置请求方式、请求地址以及是否同步
        xhr.open('POST', '/Default2.aspx', true);
        //设置请求结果类型为blob
        xhr.responseType = 'blob';
        //请求成功回调函数
        xhr.onload = function(e) {
            if (this.status == 200) {//请求成功
                //获取blob对象
                var blob = this.response;
                //获取blob对象地址,并把值赋给容器
                $("#img1").attr("src", URL.createObjectURL(blob));
            }
        };
        xhr.send();
    </script>

回到网页查看img标签src的地址:

8.文件二次渲染

$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";
                $newimagepath = UPLOAD_PATH.$newfilename;
                imagejpeg($im,$newimagepath);
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.$newfilename;
                @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";
                $newimagepath = UPLOAD_PATH.$newfilename;
                imagepng($im,$newimagepath);
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.$newfilename;
                @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";
                $newimagepath = UPLOAD_PATH.$newfilename;
                imagegif($im,$newimagepath);
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.$newfilename;
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }
    }else{
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}

绕过方法:

对比上传前后的两个文件,找到没有变动的区域,插入php代码。

参考文章:

https://wiki.wgpsec.org/knowledge/ctf/uploadfile.html

https://blog.csdn.net/cldimd/article/details/104992488

https://github.com/c0ny1/upload-labs

https://juejin.cn/post/6989580413333159949

https://blog.csdn.net/liguangyao213/article/details/123430199

https://blog.csdn.net/qq_43277152/article/details/113373721

https://blog.csdn.net/qq_45570082/article/details/108910162

https://xz.aliyun.com/t/3941

https://blog.csdn.net/weixin_64551911/article/details/124627363?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-124627363-blog-105068212.pc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-124627363-blog-105068212.pc_relevant_recovery_v2&utm_relevant_index=5

等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值