0%

文件上传漏洞

前言

什么是文件上传?

将客户端数据以文件形式封装,通过网络协议发送到服务端,保存在服务端中,在硬盘上作为真实文件形式进行保存。

文件上传功能本身没有问题,是正常的业务需求,但有问题的是文件上传后,服务器怎么处理,解释文件,这样就造就了文件上传漏洞(file up load vulnerability)

正文

大多数情况下,文件上传漏洞一般都是指上传一个web脚本能够被服务器解析,也就是webshell的问题。要完成这个,要满足一下几个条件:

  1. 上传的文件要能够被web容器所解析。那么上传文件所在的目录要是web容器所覆盖的路径。
  2. 用户能够从web上访问这个文件。
  3. 文件内容不能被安全检查,格式化,图片压缩等改变了内容,否则服务器解析不了文件。

之前的博客中我有关于文件上传漏洞的内容——“渗透测试实战–家用电脑”

(现在回头来看简直是小白中的小白,我都不知道自己是如何敢取这个名字的)

简单来说如果发现客户端对用户上传文件没有进行过滤审查或者审查不严,就有可能造成文件上传漏洞。

安全检测

一般的开发者都会对用户上传文件进行检测,而一般的检测方式为:

  1. 客户端JavaScript检测
  2. 服务端MIME类型检测
  3. 黑名单扩展名过滤
  4. 白名单扩展名过滤
  5. 文件内容检测

这里分别对其进行介绍和其绕过方式的介绍

客户端JavaScript检测

这种方式对一般用户来说可以限制住其上传的文件类型,但是对于攻击者或者稍微懂技术的人员来说,简直形同虚设。

我们用upload-labs进行举例:

这个是pass-01中的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}

这种只在前端使用JavaScript进行验证可以通过多种方式进行绕过,这里我使用的是burpsuite

我们可以将写的webshell(关于webshell写法上面的博客链接中有说到,此处不谈)文件后缀改成合适的,比如jpg,png,gif等,然后burpsuite将http包截获并修改包内文件后缀名:

GtEQuq.png

将此处文件后缀名修改成php就行

GtEoa8.png

改成php后缀名就可以绕过前端检测,客户端验证只是用来防止用户输入错误,减少服务器开销,而真正的验证还得看服务端验证。

服务端MIME类型检测

在介绍这种验证方式之前,先得介绍一下MIME类型是个啥

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。

MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。

媒体类型通常是使用 HTTP 协议,通过 Content-Type 来表示的,例如:

Content-Type: text/HTML

媒体类型对文件传输时文件类型进行了标注

刚好upload-labs中pass-02就是一个MIME类型检测,代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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.'文件夹不存在,请手工创建!';
}
}

可以发现其中判断语句为

if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif'))

发现只有三种MIME类型通过验证image/jpeg image/png image/gif

其实也可以通过我们在pass-01中的方法进行绕过,因为在上传jpg时,客户端会在发送http请求时,将Content-Type字段置为image/jpeg

Gtgf41.png

当然也可以上传php文件,然后截获http包将其Content-Type:application/php改为Content-Type:image/jpeg

黑名单扩展名过滤

顾名思义,黑名单机制就是将一些禁止上传的文件的后缀名对比上传文件的后缀名,要是有符合的,则禁止上传该文件

这里用upload-labs的pass-03示例(不得不说upload这个项目确实可以,将大部分的文件上传防御机制都包含了,可以在这里练习如何绕过检测)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$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文件后缀名

但是黑名单机制是一种不安全的方式,攻击者可以通过多种方式进行绕过:

  1. 攻击者可以找到开发人员忽略的扩展名,比如:.phtml .phps .php5 .pht

    前提是apache的httpd.conf中有配置别名解析

    1
    AddType application/x-httpd-php .phtml .phps .php5 .pht
  2. 上传.htaccess文件,但是需要在httpd.conf

    1.mod_rewrite模块开启

    2.AllowOverride All

    此时就可以自定义apache将谁用php进行解析(注意上传时一定确保.htaccess文件名称为.htaccess

    .htaccess文件(或者”分布式配置文件”),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法,
    即在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。

    可以如下使用:

    1
    2
    3
    <FilesMatch "指定文件名">
    SetHandler application/x-httpd-php
    </FilesMatch>

    也可以如下使用:

    1
    AddType application/x-httpd-php .指定文件后缀名,可以自定义后缀名
  3. Windows平台对文件扩展名的大小写不敏感,如果服务端没有进行大小写过滤,那就可以通过大小写文件后缀名进行绕过

  4. 在Windows平台,如果文件名以.或者空格作为结尾,系统会自动去除,利用此特性也可以绕过黑名单检测(注意文件后缀名可以在burpsuite里改)

  5. 在Windows平台上可以上传.php::$DATA绕过(原理是NTFS文件系统包括对备用数据流的支持,主要包括提供与Macintosh文件系统中的文件的兼容性。备用数据流允许文件包含多个数据流。每个文件至少有一个数据流。在Windows中,此默认数据流称为:$ DATA。)

白名单扩展名过滤

白名单的过滤方式与黑名单的恰恰相反,黑名单是规定不允许上传的文件扩展名,而白名单则是定义允许上传文件扩展名,白名单相比较黑名单安全性大大提高,但是也存在绕过风险,严格来说我之前说的MIME类型检测也是一种白名单机制。

还是用到upload举例,这里看Pass-12:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$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(10, 99).date("YmdHis").".".$file_ext;

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

其中白名单设置为:$ext_arr = array('jpg','png','gif');

发现只允许了 'jpg','png','gif'三种后缀名出现

这个时候绕过的方式就可以根据解析漏洞配合,具体的服务器有不同的解析漏洞,比如说是IIS6.0的目录解析漏洞和WebDav漏洞,以及Apache解析漏洞,等等

在Pass-12题中可以用到%00截断(关于00截断不止下面这种)

可以看到代码中有这两个部分$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;move_uploaded_file($temp_file,$img_path)

在PHP的的有些版本中move_uploaded_file()函数存在漏洞,其exp为move_uploaded_file($_FILES['x']['tmp_name'],"/tmp/test.php\x00.jpg")

这里有关于这个漏洞的描述:PHP任意文件上传漏洞CVE-2015-2348浅析

Pass-12save_path变量是可以可以get传参的,所以我们只需要在url中加入类似?save_path=./upload/cmd.php%00,上传文件名类似于cmd.jpg就能将文件保存在./upload/cmd.php

文件内容检测

这也是一种检测方式,通常利用文件头校验原理,对用户上传的文件内容进行检测,判定其文件类型。

这种检测方式的绕过一般上传图片马,配合文件包含漏洞,获取webshell。在文件包含漏洞中,对包含文件的类型无要求,只要包含文件中含有PHP代码即可执行。

使用CMD制作一句话木马。
参数/b指定以二进制格式复制、合并文件; 用于图像类/声音类文件
参数/a指定以ASCII格式复制、合并文件。用于txt等文档类文件
copy 1.jpg/b+1.php 2.jpg
//意思是将1.jpg以二进制与1.php合并成2.jpg
那么2.jpg就是图片木马了。

这里用upload-labs Pass-14进行举例,服务端过滤代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读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(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

其中就是对文件内容进行了检测,我们制作图片马进行绕过

Ga6eOO.png

可以看到图片16进制中含有php代码了

GaR0bT.png

可以看到配合文件包含漏洞执行了php代码

GaR5VO.png

Pass-14Pass-15以及Pass-16都可以用上述的图片马进行绕过,因为他们的原理都是对文件头类型进行检测识别,但是对于Pass-17就不同,Pass-17是对图片进行二次渲染,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
$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);

// 获得上传文件的扩展名
$fileext= substr(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的图片文件!";
}
}

大致意思是服务端端调用了php的GD库,提取了文件中的图片数据,然后再重新渲染,这样图片中插入的恶意代码就会被过滤掉了

这种过滤方式不管是直接修改文件头来制作的图片马,还是利用copy命令制作的图片马,都无法避免其中的一句话被过滤掉。

其实绕过的话就要把一句话插入到图片数据中,这样经过渲染后这部分数据还是会保留下来。可以参考这篇:

BookFresh Tricky File Upload Bypass to RCE

可以看到其所用到的POC.gif中含有php代码:

Gahhq0.png

解析漏洞

什么是解析漏洞?

解析漏洞指的是在服务器中间件上的问题,要想获得webshell,那一定要让服务器将攻击者上传的文件当作后端脚本来解析,但是通常的后端具有检测技术,可以检测到攻击者上传的webshell,但是开发人员如果仅从代码角度去防止webshell,那么他就忽略了服务器中间件的漏洞危害,中间件像Apache,Nginx,IIS,Tomcat等,在有些版本中存在解析漏洞,可以使攻击者利用这些解析漏洞,绕过开发人员的检测,实现非法文件的解析。

Apache

在Apache 1.x和2.x中存在解析漏洞 ,解析文件的规则是从右到左开始判断解析,如果后缀名为不可识别文件解析,就再往左判断,如果都不认识,那么就暴漏其源代码。

因此可以上传一个test.php.qwea文件绕过验证且服务器依然会将其解析为php文件。

Apache能够认识的文件在mime.types文件里。

G0mulV.png

IIS

IIS 6在解析文件时,也出现过一些漏洞

  1. 在IIS 6和Windows环境下出现过;字符截断文件名的问题

    当文件名为: shell.asp;xx.jpg时,IIS 6会将此文件解析为shell.asp,文件名被截断了,从而导致脚本被执行

    所以当上传文件shell.asp;xx.jpg时,检测程序只会检测出后缀名为.jpg,如果不讲文件重命名存入服务器中,那么攻击者就可以访问shell.asp;xx.jpg,其中IIS 6会将文件当成.asp脚本解析。

  2. IIS 6中还有一个漏洞就是目录解析的问题,因为处理文件夹扩展名出错,导致将/*.asp/目录下所有文件都作为ASP文件进行解析。

    例如:建立shell.asp文件夹,在文件夹中新建一个文本文档test.txt,其中包含诸如<%=NOW()%>的asp代码,那么在访问例如http://www.example.com/shell.asp/test.txt,就会执行该代码。

  3. 在IIS中,还存在一种经典漏洞,就是WebDavWebDav是一种基于HTTP 1.1协议的通信协议,它扩展了HTTP协议,使其在标准方法外新增了一些新的方法。

    在开启WebDav扩展的服务器中,如果支持PUT``Move``Copy``Delete等方法,就有可能存在安全隐患

    比如:可以先用OPTIONS先探测服务器所支持的HTTP方法,然后使用PUT方式上传脚本文件,再用Move或者Copy对文件进行改名,使之能被服务器所解析。

更多的解析漏洞可以看这篇博客,我的学识浅薄,目前也在学,之后要是碰到了新的关于文件上传的漏洞也会不断更新学习。