Web通用漏洞--文件上传
阅读原文时间:2023年08月14日阅读:5

Web通用漏洞--文件上传

文件上传安全指的是攻击者通过利用上传实现后门的写入连接后门进行权限控制的安全问题,对于如何确保这类安全问题,一般会从原生态功能中的文件内容,文件后缀,文件类型等方面判断,但是漏洞可能不仅在本身的代码验证逻辑中出现安全问题,也会在语言版本,语言函数,中间件,引用的第三方编辑器等存在缺陷地方配合利用。另外文件上传也有多个存储逻辑,不同的文件存储方案也会给攻击者带来不一样的挑战!

靶场upload-labs-docker

采用前端代码进行文件后缀验证

绕过方法

  1. 删除前端代码

    通过查看前端代码,发现上传form表单有个javascirpt事件,即检测文件后缀白名单,删除即可

  2. 在浏览器禁用javascript

  3. 将文件后缀修改为允许上传的后缀,通过前端校验后抓包修改文件后缀

上传.htaccess文件添加配置解析规则,将png文件后缀进行php解析

AddType application/x-httpd-php .png

上传写有php代码的png文件,访问

源码

    <?php
        header("Content-type: text/html;charset=utf-8");
        error_reporting(0);
        //设置上传目录
        define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
        define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));
        if (!file_exists(UPLOAD_PATH)) {
            mkdir(UPLOAD_PATH, 0755);
        }
        $is_upload = false;
        if (!empty($_POST['submit'])) {
            if (!in_array($_FILES['file']['type'], ["image/jpeg", "image/png", "image/gif", "image/jpg"])) {
                echo "<script>black();</script>";
            } else {
                $name = basename($_FILES['file']['name']);
                if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {
                    $is_upload = true;
                } else {
                    echo "<script>alert('上传失败')</script>";
                }
            }
        }
    ?>

代码中通过查看type白名单进行过滤

通过抓包将数据包中的content-type进行修改即可上传成功

源码

 <?php
        header("Content-type: text/html;charset=utf-8");
        error_reporting(0);
        //设置上传目录
        define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
        define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));
        if (!file_exists(UPLOAD_PATH)) {
            mkdir(UPLOAD_PATH, 0755);
        }
        $is_upload = false;
        if (!empty($_POST['submit'])) {
            if (!$_FILES['file']['size']) {
                echo "<script>error();</script>";
            } else {
                $file = fopen($_FILES['file']['tmp_name'], "rb");
                $bin = fread($file, 4);
                fclose($file);
                if (!in_array($_FILES['file']['type'], ["image/jpeg", "image/jpg", "image/png", "image/gif"])) {
                    echo "<script>black();</script>";
                } else if (!in_array(bin2hex($bin), ["89504E47", "FFD8FFE0", "47494638"])) {
                    echo "<script>black();</script>";
                } else {
                    $name = basename($_FILES['file']['name']);
                    if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {
                        $is_upload = true;
                    } else {
                        echo "<script>error();</script>";
                    }
                }
            }
        }
    ?>

源码中对上传文件的类型和文件头都进行了检测

if (!in_array($_FILES['file']['type'], ["image/jpeg", "image/jpg", "image/png", "image/gif"]))
if (!in_array(bin2hex($bin), ["89504E47", "FFD8FFE0", "47494638"]))

那么将我们上传的文件头添加符合上传规则文件头并且修改数据包中的content-type类型即可成功上传

题目中源码采用了黑名单验证的方式,将黑名单中的后缀进行替换为空,但是并没有进行循环验证处理,因此可以采用多重后缀名嵌套进行上传。

在linux系统中文件名大小写敏感,题目中源码只对小写后缀名进行过滤,可以采用大写文件名后缀进行上传。

零零截断即在系统进行文件接受时,会错误得把%00当作结束符进行接受保存,从而不对%00后面的字符进行保存。%00使url进行解码后的结果其实是为空字节。

 <?php
        header("Content-type: text/html;charset=utf-8");
        error_reporting(0);
        //设置上传目录
        define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
        define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));
        if (!file_exists(UPLOAD_PATH)) {
            mkdir(UPLOAD_PATH, 0755);
        }
        $is_upload = false;
        if (!empty($_POST['submit'])) {
            $name = basename($_FILES['file']['name']);
            $info = pathinfo($name);
            $ext = $info['extension'];
            $whitelist = array("jpg", "jpeg", "png", "gif");
            if (in_array($ext, $whitelist)) {

                $filename = rand(10, 99) . date("YmdHis") . "." . $ext;
                $des = $_GET['road'] . "/" . $filename;

                if (move_uploaded_file($_FILES['file']['tmp_name'], $des)) {
                    $is_upload = true;
                } else {
                    echo "<script>black();</script>";
                }
            } else {
                echo "文件类型不匹配";
            }
        }
    ?>

在文件进行保存的时候,文件路径与文件名相连接,路径中带有%00,于是将文件保存为2.php

关于php版本是否符合,在网站的返回数据包中可以查看

POST型在进行零零截断的时候需要将文件路径写在传送数据的位置,由于GET型在写入%00时,系统会自动将地址栏的%00进行解码,但是POST不会,因此在进行上传过程中需要将%00进行解码发送。

在网站管理员进行网站配置过程中没有严格进行配置网站,导致php3,php5等文件后缀名文件也进行php解析。

<?php
        header("Content-type: text/html;charset=utf-8");
        error_reporting(0);
        //设置上传目录
        define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
        define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));
        if (!file_exists(UPLOAD_PATH)) {
            mkdir(UPLOAD_PATH, 0755);
        }
        $is_upload = false;
        if (!empty($_POST['submit'])) {
            $name = basename($_FILES['file']['name']);
            $ext = pathinfo($name)['extension'];
            $blacklist = array("asp","aspx","php","jsp","htaccess");

            if (!in_array($ext, $blacklist)) {
                if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {
                    $is_upload = true;
                } else {
                    echo "<script>error();</script>";
                }
            } else {
                    echo "<script>black();</script>";
            }
        }
    ?>

源码中采取了白名单验证后缀名,如何后缀名为php,asp等将不能通过验证

条件竞争是利用一种错误的逻辑进行的文件上传,所以可以将上传的文件中写入访问即创建的代码,再对该文件进行不断上传,同时不断访问该文件,只要访问到了该文件,便会创建一个后门文件出来。

 <?php
        header("Content-type: text/html;charset=utf-8");
        error_reporting(0);
        //设置上传目录
        define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
        define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));
        if (!file_exists(UPLOAD_PATH)) {
            mkdir(UPLOAD_PATH, 0755);
        }
        $is_upload = false;
        if (!empty($_POST['submit'])) {
            $name = basename($_FILES['file']['name']);
            $ext = pathinfo($name)['extension'];
            $upload_file = UPLOAD_PATH . '/' . $name;
            $whitelist = array('jpg','png','gif','jpeg');

            if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {
                if(in_array($ext,$whitelist)){
                    $rename_file = rand(10, 99).date("YmdHis").".".$ext;
                    $img_path = UPLOAD_PATH . '/'. $rename_file;
                    rename($upload_file, $img_path);
                    $is_upload = true;
                }else{
                    echo "<script>black();</script>";
                    unlink($upload_file);
                }
            }
        }
    ?>

源码中先将文件进行存储,再进行判断文件是否合法。

首先抓取上传文件的数据包

发送至intruder模块进行空值爆破,无限制得上传该文件

再抓取访问该文件得数据包,依旧进行空值爆破

当访问数据包出现不同长度的返回包时,大概率时已经成功创建了,连接后门验证结果即可

连接成功

在进行文件上传过程中,很多网站会对上传后的图片进行二次渲染以适应web显示界面等。

在对绕过二次渲染的图片马中,gif文件最为简单

先将图片进行上传,然后将上传过后的图片进行下载,与源文件进行对比,在没有修改的部分插入代码即可。

经过对比灰色部分就是没有发生变化的部分,在灰色部分插入代码即可

重新上传,再次下载查看payload是否存在

使用网站本身的包含漏洞进行验证

在保存文件名后面加上/.便可以上传成功

不会!

  1. 执行权限(有可能绕过)

    文件上传后,存储文件的文件夹无执行权限

  2. 解码还原(不能绕过)

    文件在上传过程中,源码通过函数将文件中的数据提取出来并进行编码,存储入数据库中。在使用文件时,调用数据编码进行解码还原。

  3. 分站存储(不能绕过)

    网站与存储上传文件属于不同站点、不同服务器,在网站服务器进行上传,传输至存储文件服务器,一般会设置执行权限。

  4. 对象存储(不能绕过)

    OSS对象云存储,只能存储文件,并不能执行。具有key泄露的风险。

以上内容仅作学习,如有错误或瑕疵,欢迎批评指正,感谢阅读。