無限可能

个人邮箱:985885413@qq.com

0%

文件上传漏洞总结

前言

本文主要总结文件上传漏洞。文中部分内容直接或间接引用于其他大牛的文章中,我都会标明出处,如有侵权,请发邮箱联系我删除。多有不合理之处,望大佬指点。


漏洞原理

文件上传漏洞非常常见,是web安全中经常利用的一种漏洞形式。我们经常可以见到在web应用程序中,许多都会允许上传图片、文本或者其他资源到指定的位置,文件上传漏洞就是通过利用这些可以上传的地方,将恶意代码植入到服务器中,再通过url去访问,从而执行代码来达到恶意攻击的目的。造成文件上传漏洞的原因有很多,比如对上传文件的后缀或扩展名没有做较为严格的限制,对于上传文件的MIMETYPE没有做检查,又或者没有限制对于上传的文件的文件权限,都有可能会导致文件上传漏洞。


漏洞常见类型

基于文件校验的漏洞

客户端检测

js前端验证

前端一般会用JS函数来验证上传文件的扩展名之类的操作,相比起安全措施,这更像是一种防止用户上传操作失误的措施,我们直接删除掉JS代码,或者使用burp一类的工具进行改包,就可以绕过这类验证。

服务端检测

检查后缀/扩展名

对于在服务端进行扩展名检查的,一般分为两种:黑名单和白名单,其中后者相对更安全一点,不容易出现疏漏。

黑名单
寻找可解析后缀

利用burpsuite工具截断HTTP请求,利用intruder模块进行枚举后缀名,来寻找黑名单中是否存在没有过滤到的后缀名。

常见的可解析后缀名有:

1
".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini"
.htaccess

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

在apache中,如果需要启动.htaccess,必须在http.conf中设置AllowOverride为All。

如果我们上传一个.htaccess,其内容为:

1
SetHandler application/x-httpd-php

这样的话所有文件都会被解析为php。

注意由于直接创建txt文档会显示必须键入文件名,所以可以采用cmd命令方式来生成.htaccess

利用各类符号绕过后缀名

当这些后缀都被拉入黑名单时,我们可以考虑用大小写、双写、点号等方式来绕过。大小写、双写、结尾加空格等常规方式就不再赘述,主要说一下点绕过、::$DATA绕过、路径拼接绕过这些绕过方法。

点绕过

windows系统下,后缀名的最后一个点会被自动删除掉,所以可以通过在结尾加一个点的方式绕过黑名单。

::$DATA绕过

是Windows下NTFS文件系统的一个特性,即NTFS文件系统的存储数据流的一个属性 DATA 时,就是请求 a.asp 本身的数据,如果a.asp 还包含了其他的数据流,比如 a.asp:lake2.asp,请求 a.asp:lake2.asp::$DATA,则是请求a.asp中的流数据lake2.asp的流数据内容。

因此我们在后缀名中添加::$DATA即可绕过。

注意要在Linux下修改文件名,因为在Windows下创建不了。

路径拼接绕过

示例:

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
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$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.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

可以看到这里对文件名进行了处理,删除了文件名末尾的点,并且把处理过的文件名拼接到路径中。

这里我们就可以将文件名构造为 1.php. . (点+空格+点),这样在路径拼接之后,文件名就会变成1.php,从而成功绕过。

白名单
MIME绕过

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当改扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开,多用于指定一些客户端自定义的文件夹,以及一些媒体文件打开方式。

我们来看一个示例:

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

在这里我们检查Content-type,burp抓包修改其类型即可绕过。

Content-type常见类型见下:

文件类型 Content-type
超文本标记语言文本 .html,.html text/html
普通文本 .txt text/plain
RTF文本 .txt text/plain
GIF图形 .gif image/gif
JPEG图形 .jpeg,.jpg image/jpeg
au声音文件 .au audio/basic
MIDI音乐文件 .mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
00截断

00截断是很常见的截断方法,原理是有些函数处理时,会把这个字符当作结束符。

1
截断条件:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态

如:1.php0x00.jpg

值得注意的是,在GET型00截断中,我们要使用%00来截断,因为GET型提交的内容会被自动进行URL解码;而在POST型00截断中,%00是不会被自动解码的,我们需要在16进制中对其进行修改,将其改为00即可截断。

检查内容

文件幻数/文件头检测

文件幻数是用来唯一标识文件类型的一系列数字(十六进制),也就是我们常说的文件头,当白名单限制了文件幻数时,我们就要给我们的文件制造可以过关的文件头:

1
2
3
4
5
.jpg	Value = FF D8 FF E0 
.gif Value = 47 49 46 38
.png Value = 89 50 4E 47
.html Value = 68 74 6D 6C 3E 10
.xml Value = 3C 3F 78 6D 6C

然后在文件幻数之后加上我们要的木马就好了。

制作图片马

当服务端会对文件进行加载测试、渲染测试时,我们就可以将我们的代码注入到图片中,这样就不会破坏文件的格式,从而在白名单允许的图片格式范围内成功插入php代码,然后再利用文件包含来执行该代码。

一句话木马(webshell)中常用的危险函数有eval()assert()preg_replace()

制作方法:

​ 文件包含漏洞请见:https://jiulin.space/2021/01/02/php3/#more

基于文件处理的漏洞

条件竞争

当网站的文件上传的过程是:服务器获取文件–>保存上传临时文件–>重命名移动临时文件 这样的步骤时,

我们就可以使用条件竞争来进行绕过,通过不断地对文件进行上传和打开,从而使服务器还未重命名移动临时文件时,我们就利用时间差打开了文件,成功执行其中的php代码。

具体方法:我们可以利用burp不断发包来进行条件竞争,也可以通过python脚本来反复发送请求得到结果。

二次渲染

二次渲染,相当于把原本属于图像数据的部分抓了出来,再用自己的API 或函数进行重新渲染。在该过程中,非图像数据的部分与图像数据会被隔离开。

方法也很简单,我们在十六进制中寻找二次渲染并未改变的部分,在其中插入php代码就好了。

基于文件解析的漏洞

IIS篇

;截断

当文件名为abc.asp;xx.jpg时,IIS6会将此文件解析成abc.asp,文件名被截断了,从而导致脚本被执行。

处理文件夹拓展名出错

在IIS6.0的网站目录中创建有*.asp形式的目录,该文件夹下的所有文件都会以asp脚本格式进行解析。

WebDav漏洞

WebDav是一种基于HTTP1.1协议的通信协议。在GET、POST、HEAD等HTTP标准方法之外扩展了新方法。

攻击者可以通过PUT方法向服务器上传危险脚本。

NGINX篇

php配置错误导致的解析漏洞

我们用一个test.jpg(其内容为一句话木马)来构造payload:

1
http://127.0.0.1/test.jpg/test.php

Nginx在拿到/test.jpg/test.php后,一看后缀是.php,便认为该文件是php文件,转交给php去处理。php一看/test.jpg/test.php不存在,便删去最后的/test.php,又看/test.jpg存在,便把/test.jpg当成要执行的文件了。

原理是,当打开php的一个选项:cgi.fix_pathinfo,该值默认为1,表示开启。之后,php就对文件路径进行”修理“,当php遇到文件路径“/aaa.xxx/bbb.yyy/ccc.zzz”时,若“/aaa.xxx/bbb.yyy/ccc.zzz”不存在,则会去掉最后的“/ccc.zzz”,然后判断“/aaa.xxx/bbb.yyy”是否存在,若存在,则把“/aaa.xxx/bbb.yyy”当做文件“/aaa.xxx/bbb.yyy/ccc.zzz”,若“/aaa.xxx/bbb.yyy”仍不存在,则继续去掉“/bbb.yyy”,以此类推。

新版本的php引入了“security.limit_extensions”,限制了可执行文件的后缀,默认只允许执行.php文件。我们可以考虑修改该文件中的“security.limit_extensions”,添加上.jpg,这样php就认为.jpg也是合法的php文件了。

APACHE篇

多后缀名

Apache认为一个文件可以有多个后缀,如:example.php.abc,Apache会从右往左辨别后缀,一开始看到这个abc,不认识所以读到了php, 就把我们输入的这个文件当作了php文件,不再继续往下读。

换行解析漏洞

此漏洞的出现是由于apache在修复第一个后缀名解析漏洞时,用正则来匹配后缀。在解析php时xxx.php\x0A将被按照php后缀进行解析,导致绕过一些服务器的安全策略。

用法是在用burp抓包后在例如1.php后插入以一个\x0A绕过黑名单过滤,访问1.php%0A,即可看到文件被当作php解析。

此外,文件上传漏洞也经常用于和文件包含漏洞打”组合拳“,也就是先上传恶意代码,再包含上传文件。


文件上传漏洞防御思路

引自:美创安全实验室

系统运行时的防御

  1. 文件上传的目录设置为不可执行。只要web容器无法解析该目录下面的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响,因此这一点至关重要。
  2. 判断文件类型。在判断文件类型时,可以结合使用MIME Type、后缀检查等方式。在文
    件类型检查中,强烈推荐白名单方式,黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码。
  3. 使用随机数改写文件名和文件路径。文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用了随机数改写了文件名和路径,将极大地增加攻击的成本。再来就是像shell.php.rar.rar和crossdomain.xml这种文件,都将因为重命名而无法攻击。
  4. 单独设置文件服务器的域名。由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传crossdomain.xml、上传包含Javascript的XSS利用等问题将得到解决。
  5. 使用安全设备防御。文件上传攻击的本质就是将恶意文件或者脚本上传到服务器,专业的安全设备防御此类漏洞主要是通过对漏洞的上传利用行为和恶意文件的上传过程进行检测。恶意文件千变万化,隐藏手法也不断推陈出新,对普通的系统管理员来说可以通过部署安全设备来帮助防御。

系统开发阶段的防御

  1. 系统开发人员应有较强的安全意识,尤其是采用PHP语言开发系统。在系统开发阶段应充分考虑系统的安全性。
  2. 对文件上传漏洞来说,最好能在客户端和服务器端对用户上传的文件名和文件路径等项目分别进行严格的检查。客户端的检查虽然对技术较好的攻击者来说可以借助工具绕过,但是这也可以阻挡一些基本的试探。服务器端的检查最好使用白名单过滤的方法,这样能防止大小写等方式的绕过,同时还需对%00截断符进行检测,对HTTP包头的content-type也和上传文件的大小也需要进行检查。

系统维护阶段的防御

  1. 系统上线后运维人员应有较强的安全意思,积极使用多个安全检测工具对系统进行安全扫描,及时发现潜在漏洞并修复。
  2. 定时查看系统日志,web服务器日志以发现入侵痕迹。定时关注系统所使用到的第三方插件的更新情况,如有新版本发布建议及时更新,如果第三方插件被爆有安全漏洞更应立即进行修补。
  3. 对于整个网站都是使用的开源代码或者使用网上的框架搭建的网站来说,尤其要注意漏洞的自查和软件版本及补丁的更新,上传功能非必选可以直接删除。除对系统自生的维护外,服务器应进行合理配置,非必选一般的目录都应去掉执行权限,上传目录可配置为只读。

防护代码参考

引自DVWA中impossible源码

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

if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {

// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );

// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}

// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

//1. strtolower()函数,对字符串进行小写操作,防止了用大小写绕过
//2. uniqid()函数,基于以微秒计的当前时间,生成一个唯一的ID
//3. $target_file = md5( uniqid() . $uploaded_name ) . ‘.’ . $uploaded_ext;:对上传的文件进行了重命名,为md5值,导致00截断无法绕过过滤规则
//4. 通过imagecreatefromjpeg()和imagecreatefrompng()函数将上传的图片文件重新写入到一个新的图片文件中,这两个函数会自动将图片中的有害元数据抹除,因此即使黑客上传了一张图片马也会被这个函数过滤成一个纯正的图片。
//5. imagedestroy( $img )将用户上传的源文件删除
//6. unlink( $temp_file )删除过滤过程中产生的任何临时文件