文件上传漏洞原理与绕过总结

文件上传漏洞

漏洞原理

先看一下一般文件上传的功能是怎么实现的

前端校验

1.创建表单,用于上传文件

<div id="upload">
    <form action="upload_file.php" method="post" enctype="multipart/form-data" onsubmit="return CheckFile()">
        <label for="file">头像:</label>
        <input type="file" name="file" id="file">
        <input type="submit" name="submit" value="提交">
    </form>
</div>

2.js代码校验文件

// 以下JS代码功能为检查文件后缀名合法性
function CheckFile(){
    var file = document.getElementById('file').value;
    if(file == "" || file == null) {
    alert('请选择您要上传的文件!');
    return false;
    }
    // 定义文件上传的扩展名
    var allow_ext = ".jpg|.png|.gif|.jpeg|.jiff";
    // 提取上传文件的扩展名
    var ext_name = file.substring(file.lastIndexOf("."));
    // 判断上传文件是否符合要求
    if (allow_ext.indexOf(ext_name) == -1){
    var msg = "请输入" + allow_ext + "类型的文件!";
    alert(msg);
    return false;
    }
}
/**
Extension:
substring(start, end):截取字符串,参数为下标;只填一个参数代表从该下标开始到结束
indexOf():返回参数所在的下标位置,若目标不存在则返回-1
lastIndexOf():返回该参数最后一个下标位置
*/

3.后端上传功能实现 upload_file.php

<?php

if($_FILES['file']['error'] > 0){
    echo $_FILES['file']['error'];
}

/* 
echo "上传文件名: " . $_FILES["file"]["name"] . "<br>";
echo "文件类型: " . $_FILES["file"]["type"] . "<br>";
echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB<br>";
echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"]; 
*/

// 保存上传的文件

// 判断文件是否存在
if (file_exists("upload/" . $_FILES['file']['name'])){
    echo "文件已存在";
}
else{
    // 将文件保存到指定目录
    $filename = "upload/" . date("Ymd_His.") . end(explode('.', $_FILES['file']['name']));
    // move_uploaded_file(file,newloc) file:要移动的文件 newloc:文件的新位置。
    move_uploaded_file($_FILES['file']['tmp_name'], $filename) or die('上传失败');
}
echo "<script>alert('上传成功!')</script>";
echo "<script>location.href='./list.php'</script>";

?>

后端校验

// 判断后缀名
$allowExts = array("jpg", "jpeg", "png", "gif");
$ext_name = end(explode('.', $_FILES['file']['name']));
if (!in_array($ext_name, $allowExts)){
    echo "<script>alert('请上传 .jpg | .png | .jpeg | .png | .gif 类型的文件!')</script>";
    echo "<script>location.href='list.php'</script>";
}

// 判断MIME类型
$allowFileTypes = array("image/jpeg", "image/jpg", "image/gif", "image/png");
if (!in_array($_FILES['file']['type'], $allowFileTypes)){
    echo "<script>alert('请上传 .jpg | .png | .jpeg | .png | .gif 类型的文件!')</script>";
    echo "<script>location.href='list.php'</script>";
}

绕过方式

前端过滤绕过

1.后缀名过滤

上传时的文件后缀为合法的,例如.png,抓包修改回.php

2.MIME文件类型过滤

  • 使用F12禁用或修改Javascript(可能会影响原本页面正常的功能)

  • 使用BurpSuite抓包修改Content-Type字段值和文件后缀名

3.禁用js

禁止js可能会导致网站功能无法正常使用

黑名单绕过

1.等价扩展名

语言 等价扩展名
asp asa, cer, cdx
aspx ashx, asmx, ascx
php php2 php3 php4 phps phtml
jsp jspx jspf

利用前提:使用php2 phtml等需要配置文件中存在解析类型

image-20230428085617827

2.双写绕过

关键词过滤置空时,可以尝试双写绕过,例如SELSELECTECT

3.大小写绕过

可能只过滤了小写/大写的关键词,SeLeCT

4.点绕过

Windows中不允许文件名末尾是.,上传文件名改为shell.php.绕过过滤,写入时windows会自动将末尾的.去除

5.空格绕过(仅Windows环境下)

同上,windows会将文件名末尾空格去除

6..htaccess文件绕过

利用前提:

AllowOverride = Allmod_rewrite模块开启

.htaccess中写入

<FilesMatch "\.jpg">
    SetHandler application/x-httpd-php		
</FilesMatch>          		

上传.htaccess文件,再上传.jpg文件就会被当作php执行

7.中间件解析机制绕过

Apache

在Apache解析机制是当遇到无法识别的后缀名是,会依次取前一个后缀名。

例如上传shell.php.jjj,.jjj后缀名无法解析,因此shell.php.jjj会被当做shell.php解析

Nginx

在上传的图片马后添加/xx.php,例如shell.jpg/xx.php,上传的图片马就会被当作php执行

8.::$DATA绕过(仅Windows环境下)

使用::$DATA不会检测其后缀名,且保持::$DATA之前的文件名。例如:shell.php::$DATA会被解析为shell.php

白名单绕过

1.%00截断

利用条件:

php < 5.3.4,magic_quotes_gpc=off,上传路径可控

例如上传shell.jpg,在上传路径/upload/后加上shell.php%00,最终会被处理成/upload/shell.php/%00.jpg,%00是空字符,后面的内容会被丢弃

image-20230428171406639

2.图片马

有文件包含漏洞时,制作图片马上传(详见文末),然后通过文件包含漏洞连接shell

有些站点中会将图片二次渲染,可能会导致写入的一句话被破坏,可以将渲染后的图片下载下来,对比上传前后的图片十六进制数据,在相同即没有被修改的部分插入一句话

3.条件竞争绕过

服务器处理逻辑有问题,例如如下代码

$is_upload = false;
$msg = null;

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

问题在于,先将文件上传到服务器后,才进行白名单判断、移动、重命名等相关操作,最后再删除源文件。

因此只要上传路径可知,就可以利用文件上传到服务器还未进行操作的这个间隙,访问到我们上传的原始文件

漏洞修复

1.前后端都要校验MIME类型和后缀名

2.后缀名都要先转换为小写再校验

3.使用白名单限制

4.去掉文件名前后的空格等特殊字符

5.图片文件使用二次渲染并另存

6.对上传的文件要先重命名然后再移动,避免条件竞争

7.上传文件目录的权限设置为不可执行

图片马制作

1.文本方式或十六进制打开图片,直接插入代码

2.cmd中输入如下命令

copy 1.jpg/b + 2.php/a shell.php

3.在shell.php中添加文件头、文件尾

常见图片文件头文件尾:

文件类型 后缀 文件头 文件尾 标志
JPEG .jpg/.jpeg FFD8FF FFD9 JFIF
PNG .png 89504E47 AE426082 PNG IEND IHDR
GIF .gif 47494638 003B GIT9a
TIFF .tif/.tiff 49492A00 4D4D2A00 - II MM

posted @ 2023-11-10 08:42  Mast1n  阅读(384)  评论(0编辑  收藏  举报