Web安全防护——文件上传攻击 – 作者:magic24

在Web应用中,每个人都会接触到上传功能,以头像上传功能最为常见。例如,用户可利用上传功能上传个人照片或图片,从而自定义头像。在这个过程中,用户会上传自己的信息(通常是文件,部分情况下是远程图片地址),服务器接收到用户端的上传信息后会按照业务流程进行处理,并在后续页面中显示。
常见的头像上传功能如下图所示:

t011432dabf9070d8c4.jpg

上传功能是用户与服务器进行文件交互的重要手段,主要应用于系统的流程核心点,如证.件信息上传、申请表格上传、自定义头像上传等。通过上传功能,用户可实现自有内容的个性化修改、重点业务的开展等。同时,在业务系统侧,也可以通过文件上传功能实现用户交互的自定义设计,为业务开展及用户体验提供良好的实现方式。

但是,上传过程中存在重大安全隐患。攻击者的目标是取得当前Web服务器的权限。如果通过Web层面开展攻击,那么必须将攻击者的木马插入Web系统中,并在服务器端执行。这个过程就是对Web服务器进行文件注入攻击。这时,上传点可作为上传木马的有效途径,上传攻击将直接威胁当前系统的安全性。上传攻击可定义如下:

文件上传攻击是指攻击者利用Web应用对上传文件过滤不严的漏洞,将应用程序定义为类型范围之外的文件上传到Web服务器,并且此类文件通常为木马,在上传成功后攻击者即可获得当前的webshell。

1. 上传攻击的原理

在针对Web的攻击中,攻击者想要取得webshell,最直接的方式就是将Web木马插入服务器端进行成功解析。那么如何理解成功解析?假设目标服务器为用PHP语言构建为Web系统,那么针对上传点就需要利用PHP木马,并且要求木马在服务器以后缀名.php进行保存。因此,上传木马的过程就是在Web系统中新增一个页面。当木马上传成功后,攻击者就可远程访问这个木马文件,也就相当于浏览一个页面,只不过这个页面就是木马,具备读取、修改文件内容、连接数据库等功能。

了解木马原理之后再进一步思考,服务器肯定不能发允许这样的情况存在。因此,Web应用在开发时会对用户上传的文件进行过滤,如限制文件名或内容等。因此,上传漏洞存在的前提是:存在上传点且上传点用户可独立控制上传内容,同时上传文件可被顺利解析。在以上条件都具备的情况下,攻击者方可利用此漏洞远程部署木马,并获取服务器的Web执行权限,进而导致服务器的webshell被获取,并产生后续的严重危害。

总结来说,假设目标Web服务器的Apache+PHP架构,攻击者通过上传功能上传“木马.php”服务器,再访问“木马.php”会被当做.php文件执行,进而木马生效。

2. 上传的标准业务流程

上传功能看似简单,用户选择需上传的文件,点击上传即可。但事实上,服务器端需要进行多个步骤,方可完成整个上传流程。上传漏洞攻击的防护思路如下图所示。

图片[2]-Web安全防护——文件上传攻击 – 作者:magic24-安全小百科上图给出了上传攻击的基本业务流程以及常见的防护方式和绕过手段。上传攻击与其他攻击相比,思路较为明确,主要针对各环节中的缺陷进行尝试,并针对防护手段进行绕过。

总体来说,上传功能执行期间涉及的功能点较多,整个过程可分为三大步骤:

1)客户端上传功能

用户提交上传表单,利用HTML格式,实现上传格式的编制,再封装到HTTP包中,开始传输。

2)中间件上传功能

中间件主要有三个流程:

  • 接收客户端提交的HTML表单。
  • 将表单内容存储为临时文件。
  • 根据安全规范,将临时文件保存为正式文件。

3)服务器存储及调用

服务器会存储正式文件,并将其存放在中间件规定的真实路径中。

需要注意的是,每种网页动态语言(如PHPJSPASP)中,上传功能及可利用的木马均不同,但针对上传功能的利用思路及方式基本一致。

标准上传功能代码

上传功能目前主要通过HTML的表单功能来实现,即将要上传的文件拆分为文件名及文件内容,并由服务器进行接收,再根据中间件规则进行过滤,转存至本地存储,从而完成了一次上传功能。以下为一个标准的上传功能实现案例。

(1)客户端上传表单

在网站的上传功能中,客户端上传表单基本均通过HTML格式中的<from>实现。如下所示:

<from action= “upload_file.php” method = “post” enctype = “multipart/from-data”>
<label for = “file”>文件:</label>
<input type = “file” name = “file” id = “file”/>
<input type = “submit” name = “submit” value = “提交”/>
</from>

此部分为上传功能的<from>表单,相关分析如下:

  • from action定义了表单上传目标页面为upload_file.php。
  • 采用的提交方式由method定义,值为post。
  • 定义提交类型为multipart/form-data(如果不指定,则由浏览器自行判断)。

此外,还分别定义了文件名及相应的提交功能。

在功能上,此部分代码很好理解,即上传表单将用户提交的上传文件分割为文件名及类型,并通过post方式(method)将表单发送至目的连接(from action=后面的地址)。接下来再看看服务器端如何实现对表单信息的接收及处理。

(2)服务器端功能实现

服务器端接收到来自浏览器上传的表单后,会对上传表单中的内容进行处理,并将PHP中的文件缓存到真实路径中。服务器端代码(上节中的upload_file.php)如下所示:

<?php
	if(is_uploaded_file($_FILES["file"]["tmp_name"]))
	{
		$upfile=$_FILES["file"];
		$name=$upfile["name"];
		$type=$upfile["type"];
		$size=$upfile["size"];
		$tmp_name=$upfile["tmp_name"];

		echo "上传文件名:" . $_FILES["file"]["name"] . "<br />";
		echo "上传文件类型:" . $_FILES["file"]["type"] . "<br />";
		echo "上传文件大小:" . $_FILES["file"]["size"] / 1024 . "Kb<br />";
		$destination="../file/".$name;
		move_uploaded_file($tmp_name,$destination);

		echo "文件上传成功!";
		echo "<br>图片预览</br>";
		echo "<img src=".$destination.">";
	}
	else
	{
		echo "文件上传失败!";
	}
?>

上述代码的完整流程为:

1)upload_file.php页面接收到表单的上传信息,分别为各部分信息赋值,如$name=$upfile[“name”]就是将表单中上传文件名称赋值到$name。

2)将文件正式存储在服务器真实路径中(路径为../file/),存储过程中使用的函数为:

move_uploaded_file($tmp_name,$destination);

如果其中任何环节出问题,则报错:“文件上传失败!”。如成功,则显示“文件上传成功”,并可以在前台预览图片。

上述代码是实现上传功能的基本源码,其中未添加任何防护措施。如要在此环境下进行木马上传,直接利用上传功能选择木马文件并执行即可。

3. 上传攻击的条件

通过上面的介绍可以看到,我们能够将一个文件上传至服务器,并且其中没有任何防护手段,即服务器无法限制上传文件的类型等(文件大小由php.ini控制,默认为2MB,可通过修改php.ini的配置项进行修改)。

再回到攻击者视角,攻击者利用上传功能的目的是将Web木马上传至服务器并能成功执行。因此,攻击者成功实施文件上传攻击并获得服务器webshell的前提条件如下所示。

(1)目标网站具有上传功能

上传攻击实现的前提是:目标网站具有上传功能,可以上传文件,并且文件上传到服务器后可被存储。

(2)上传的目标文件能够被Web服务器解析执行

由于上传文件需要依靠中间件解析并执行,因此上传文件后缀应为可执行格式。在Apache+PHP环境下,要求上传的Web木马采用.php后缀名(或能有以PHP方式解析的后缀名),并且存放上传文件的目录要有执行脚本的权限。以上两种条件缺一不可。

(3)知道文件上传到服务器后存放路径和文件名称

许多Web应用都会修改上传文件的文件名称,这时就需要结合其他漏洞获取这些信息。如果不知道上传文件的存放路径和文件名称,即使上传成功也无法访问。因此,如果上传成功但不知道真实路径,那么攻击过程没有任何意义。

(4)目标文件可被用户访问

如果文件上传后,却不能通过Web访问,或者真实路径无法获得,木马则无法被攻击者打开,那么就不能成功实施攻击。

以上是上传攻击成功的4个必要条件。因此,在防护方面,系统设计者最少要解决其中一项问题,以避免上传漏洞的出现。但是在实际应用中,建议增加多道防护技术,尽量从多角度考虑,提升系统整体安全性。

4. 上传检测绕过技术

在上传过程中,既要保证上传功能的正常开展,又要对攻击者的木马情况进行过滤。接下来将对其中每项防护技术进行分析,剖析防护原理及手段,阐述该防护手段的缺陷及绕过方式等。

4.1 客户端JavaScript检测及绕过

在XSS攻击中介绍过,JavaScript可以嵌入网页中执行。那么可利用此特性设计一个特定脚本,在生成阶段对用户的上传表单进行检测。如果发现存在恶意后缀名,则不允许表单提交,并告知用户上传错误,从而实现防护效果,即限制用户上传的文件名后缀。以下简称JS防护。

上述防护思路总结为:如果用户上传文件名非法,则不允许通过;如果合法,则正常执行上传流程。

4.1.1 JS防护思路

在网站中部署JavaScript脚本,在用户访问时,脚本随同网页一起到达客户端浏览器。当用户上传文件时,JS脚本对用户表单提交的数据进行检查,如果发现非法后缀,如.php、.jsp等,则直接终止上传,从而起到防护效果。

4.1.2 JS防护代码

在上传表单中,利用onsubmit事件激活防护代码。添加的防护代码如下:

<from action=”upload1.php” onsubmit=”return lastname()”method=”post” name=”from1″ enctype=”multipart/from-data” >上传文件:<input type=”file” name=”upfile”/><br>
<input type=”submit” value=”上传” /></from>
</body>

上述代码实现提交上传文件的表单的功能。这里采用onsubmit功能(代码中加粗字体)将上传的信息返回到lastname(另一端的JavaScript进行接收),检测完成后通过返回值进行确认,执行放过或阻断的后续行为。

JS防护代码如下所示:

<script language="javascript">
	function lastname()
	{
		var strFileName=form1.upfile.value;
		if(strFileName=="")
		{
			alert("请选择要上传的文件");
			return false;
		}
		var strtype=strFileName.substring(strFileName.length-3,strFileNmae.length);
		strtype=strtype.toLowerCase();
		if(strtype=="jpg"||strtype=="gif"||strtype=="bmp"||strtype=="png")
			return ture;
		else
		{
			alert("这种文件类型不允许上传!");
			from1.upfile.focus();
			return false;
		}
	}
</script>

此段JS代码的作用是接收用户的上传参数并赋值给strFileName

  • 如果strFileName为空,则弹框提示:“请选择要上传的文件”。
  • 如果后缀名为jpg、gif、bmp、png,则判断为正确,执行上传流程。
  • 如果不是上述后缀名,则弹窗提示:“这种文件类型不允许上传!”

通过这种方式,即可实现对后缀名的完整检查。此种手段仅允许合法后缀名通过,所以也可以叫做JS白名单防护方式。

4.1.3 JS防护绕过方式

虽然上述方式实现了后缀名的检查,并且在用户端完成了检测,看似防护效果良好,其实还存在重大隐患。其一,浏览器可通过禁用JS方式,禁止防护脚本执行,导致防护功能直接失效。其二,如果这种防护手段在HTTP数据包发出之前执行完毕,那么攻击者可利用Web代理类攻击,抓取含有上传表单的HTTP数据包,并在包中将其修改为预想的后缀,如.php,则也可绕过此JS防护手段。具体绕过效果如下:

1)直接删除代码里onsubmit事件中关于文件上传时验证上传文件的相关代码。可利用浏览器的编辑功能,直接删除onsubmit,实现绕过JS防护的效果。

2)直接更改JS脚本,加入预期的扩展名。例如,在原有4中允许的后缀中加入预期的扩展名,如php,那么防护手段也就随之失效。

3)用户浏览器禁用JS功能,导致上述过滤功能直接失效。可以在浏览器的管理功能里进行相关设置,禁用JS功能。以Chrome浏览器为例,设置方法为:在浏览器中进入设置项目,选择高级设置,找到网站设置,进入后即可看到相关JS设置,选择禁用“JavaScript”即可禁用JS功能。如图所示:

1603278047_5f9014dfef6cec51abe6b.png

4)伪造后缀名绕过JS检查,并采用HTTP代理工具(burpsuite、fiddler)进行拦截,修改文件名后即可成功绕过。如图所示:

1602496969_5f8429c9068b5c5eb3b35.png

总体来说,采用JavaScript进行过滤的防护方式的初衷是减少服务器的性能消耗,同时实现防护功能。但是,这种防护手段的缺陷是:服务器要相信用户的浏览器提交的信息正确。由于检测是在客户端进行的,所以会存在很大的安全隐患,如用户可随意修改本地信息及配置,导致防护手段失效,且绕过手法较为成熟。因此,不建议只采用JavaScript手段进行校验,应配合多种方式进行综合过滤。

4.2 服务器端MIME检测及绕过

如果客户端提交信息的过程可由攻击者完全操控,就意味着由客户端提交的数据无法被完全相信,因此需要在服务器端添加相应的检测条件。既然木马是以动态脚本的方式提交并执行的,则格式需为.php。因此,可以检测客户端上传的文件类型,从而判断此次上传文件是否合法。这种检测文件类型的方法是通过检查HTTP包的Content-Type字段中的值来实现的。

4.2.1 MIME防护思路

在HTTP协议中,会利用Content-Type标识本次上传的内容类型。这个类型由客户端的浏览器根据本次上传文件的后缀名自动生成。常见的类型如下:

Content-Type 含义 Content-Type 含义
image/jpg JPG图像 text/html HTML文档

image/gif

GIF图像 application/XML XML文档

image/png

PNG图像 application/PDF PDF文档

假设本次只允许上传图像格式文件,则允许上传的文件Content-Type值为 image/jpg、 image/gif、image/png。如果攻击者上传的文件为“木马.php”,则对应类型应为text/html。服务器接收到HTTP包后,会先判断Content-Type是否合法。如果合法,则进行后续代码流程;如果非法,则直接中断本次上传。很多地方也将Content-Type称为MIME(Multipurpose Internet Mail Extensions)信息。因此,此种防护手段通常也叫作MIME检测机制。

默认情况下,Content-Type类型由浏览器自动生成,并且再HTTP包的文件头中进行传输。如下图所示:

1603279015_5f9018a74992cdca5cd61.png

由上图可知文件2.png对应的Content-Type为image/jpg,这是个合法的对应关系。

4.2.2 MIME防护代码

那么在服务器端如何实现这种防护效果呢?以PHP为例,标准方式如下:

服务器接收到上传的HTTP包后,分别对name、type、size、tmp_name进行赋值,并对$type做对比判断,如符合其中内容,则判断成功,进入后续流程,如不符合,则终止业务流程并报错。代码如下:

if(($FILES[‘upfile’][‘type’] == “image/gif”) || ($FILES[‘upfile’][‘type’] == “image/jpeg”) || ($FILES[‘upfile’][‘type’] == “image/png”) || ($FILES[‘upfile’][‘type’] == “image/bmp”))
{
if(move_upload_file($_FILES[‘upfile’][‘tmp_name’], $distination .’/’ .$_FILES[‘upflie’][‘name’]))
{
echo “上传文件名:” . $_FILES[“upfile”][“name”] . “<br />”;
echo “上传文件类型:” . $_FILES[“file”][“type”] . “<br />”;
echo “上传文件大小:” . $_FILES[“file”][“size”] / 1024 . “Kb<br />”;
echo ‘文件上传成功,保存于:’ . $distination . $_FILES[‘upfile’][‘name’] . “\n”;
echo “<br>图片预览:</br>”;
echo “<img src= “.$distination.”>”;
}
}
else
{
echo ‘文件类型不正确,请重新上传!’.”\n”;
}

其中关键代码为:

if(($FILES[‘upfile’][‘type’] == “image/gif”) || ($FILES[‘upfile’][‘type’] == “image/jpeg”) || ($FILES[‘upfile’][‘type’] == “image/png”) || ($FILES[‘upfile’][‘type’] == “image/bmp”))

其意义是:对type进行判断,如果等于image/gif、image/jpeg、image/png、image/bmp,则将上传文件转存至服务器;如果错误,则报告“文件类型不正确,请重新上传!”这样就实现了针对文件类型的检测机制。

4.2.3 MIME防护的绕过方式

由于校验通过验证MIME值完成,而Content-Type类型是由客户端浏览器自动生成的,俺么在这个过程中其实Content-Type类型是可控的。只要伪造MIME值即可绕过防护,实现木马的上传。

可利用的攻击手段如下:
HTTP抓包后修改Content-Type类型

Content-Type类型由浏览器生成,因此可将Content-Type类型替换为可被允许的类型,从而绕过服务器后台检测。可利用burpsuite进行抓包,选择上传文件,文件名为php.php,如下图示:

1603280426_5f901e2a7245f6942d767.png

再上传之前利用burpsuite抓取HTTP包,之后在页面出点击Upload。burpsuite抓包结果如图:

1603280633_5f901ef951e7d6bf6a507.png

可以看到,Content-Type类型为application/octet-stream修改为image/jpeg,再转发数据包,即可成功上传木马。

总体来说,MIME在服务器端检测,比JS在客户端的检测效果略好一些。但由于Content-Type类型依然由客户端浏览器生成,因此实际上MIME还是处于用户可控的状态。因此,MIME的防护效果依旧较差,不建议作为主要防护手段使用。

4.3 服务器端文件扩展名检测及绕过

既然用户可以操控参数,那么从理论上来说,基于客户端参数的检测手段都可以被攻击者绕过,比如上述的JS防护、MIME防护。因此,只能在服务器端进行全面检查,且不能信赖由用户浏览器生成并提交的数据。综上,有效的防护思路为:
1)当服务器接收到上传信息后,校验文件名是否合法。如果不合法,则直接丢弃,从而避免攻击者欺骗检测机制。

2)完全不信赖用户所上传文件的后缀名,在用户上传文件之后,重新给上传文件添加后缀名。

4.3.1 文件扩展名检测防护思路

文件扩展名检测的思路是:通过在服务器端检测上传文件的扩展名来判断上传文件是否合法,主要通过文件后缀名黑/白名单过滤。仅通过MIME类型判断,由于MIME类型由用户操控,其安全性较差,极易被绕过。因此,服务器端才用了针对文件名的黑/白名单检查机制,会丢弃不符合要求的文件,以提升服务器的安全性。

另一种方式是完全不信任用户所上传文件的后缀名。上传时,在转存过程中利用预先设定的后缀名保存文件来避免非法后缀名的问题。但是,由于此种方法只能指定一种后缀名,因此适用场景较为单一。

关于黑白名单的适用性,单纯从防护效果来看,白名单的防护效果好于黑名单,这与两种防护方式允许上传的范围有直接关系。白名单仅允许其支持的几种文件后缀名通过,黑名单则仅对禁止的几类文件名进行过滤,其余均放行。但是在实际应用中,白名单由于其防护过严,常常无法满足实际业务需求。因此,推荐如下几种防护手段:

(1)文件后缀重命名

只允许单一文件上传,例如针对头像上传,只允许JPG格式。(其他格式文件也可以上传,但由于后缀名会被重命名为.jpg,因此其他格式文件无法执行)

(2)白名单过滤

只允许一种类型的文件上传,如图片上传。

(3)黑名单过滤

允许多种类型文件上传,如统计表格提交、基本信息文件上传等。

总体来说,针对文件后缀名的防护方式避免了之前两种防护方式的直接缺陷,但由于防护手段严格,业务功能有局限性。如果业务相关的文件格式可指定为同一类型,那么再好不过。如果要求上传各类型文件,则不太适用。因此,在防护方案设计时,需充分考虑用户的实际业务体验,切勿一味追求安全而是用户体验下降或正常业务无法开展。

4.3.2 文件扩展名检测防护代码

针对文件名的防护,其思路为:先获取文件名,对需过滤的后缀进行赋值,并根据已经设置好的规则做对比即可。

上述三种防护手段的实现方式如下:

(1)黑名单防护

黑名单防护的特点在于禁止上传特定的文件格式,而非指定的内容可直接通过。这里以禁止上传动态网页格式为例,防护示例如下所示。

if(file_exists($distination))
{
$deny_ext = array(‘.php5′,’.php’,’.cer’,’.php4′);
$file_ext = strrchr($_FILES[‘upfile’][‘name’],’.’);
if(!in_array($file_ext,$deny_ext))
{
if(move_upload_file($_FILES[‘upfile’][‘tmp_name’], $distination .’/’ .$_FILES[‘upflie’][‘name’]))
{
echo “上传文件名:” . $_FILES[“upfile”][“name”] . “<br />”;
echo “上传文件类型:” . $_FILES[“file”][“type”] . “<br />”;
echo “上传文件大小:” . $_FILES[“file”][“size”] / 1024 . “Kb<br />”;
echo ‘文件上传成功,保存于:’ . $distination . $_FILES[‘upfile’][‘name’] . “\n”;
echo “<br>图片预览:</br>”;
echo “<img src= “.$distination.”>”;
}
}
else
{
echo ‘文件类型不正确,请重新上传!’.”\n”;
}
}

该防护手段的实现原理如下:

1)定义了一个数组$deny_ext,并用不允许通过的文件后缀名(.php5、.php、.cer、.php4)对齐进行赋值。

2)在文件的上传过程中首先取得上传文件的后缀名,并赋值为$file_ext。之后对比两个数组,如果不存在相同内容,则执行后续流程,即执行对文件的转存。如果存在交集,则提示“文件类型不正确,请重新上传!”。

(2)白名单防护

与黑名单相反,白名单只允许上传特定格式,非要求的格式则一律禁止上传。防护示例如下:

if(isset($_POST[‘upload’]))
{
$ext_arr = array(‘flv’,’swf’,’mp3′,’mp4′,’3gp’,’zip’,’rar’,’gif’,’jpg’,’png’,’bmp’);
$file_ext = substr($FILES[‘file’][‘name’],strrpos($_FILES[‘file’][‘name’],”.”)+1);
if(in_array($file_ext,$ext_arr))
{
$tempFile = $_FILES[‘file’][‘tmp_name’];
//这句话的$_REQUEST[‘jieduan’]造成可以利用截断上传
$strgetPath = $_SERVER[‘DOCUMENT_ROOT’].”/”.$_REQUEST[‘jieduan’].rand(10,99).data(“YmdHis”).”.”.$file_ext;
if(move_uploaded_file($tempFile,$targetPath))
{
echo ‘上传成功’.'<br>’;
echo ‘路径:’.$targetPath;
}
else
{
echo(“上传失败”);
}
}
else
{
echo(“上传失败”);
}
}

白名单上传的原理与黑名单基本相同,只不过是在对比环节中,如果符合设定类型,则允许通过。因此不再对上述代码进行详细分析。

(3)文件重命名防护

文件重命名是一种极端情况,常用于对上传文件的严格限制。以上传头像功能为例,其业务只要求用户上传后缀名为.jpg图像的文件,那么服务器在接收到上传信息之后,在转存过程中直接丢弃原有后缀名,并添加.jpg,实现文件重命名方案。

下面是一个防护示例:

$filerename = ‘jpg’;
$newname = md5 (uniqid (microtime(7))).
‘-‘.$filename;
if(move_uploaded_file($_FILES[‘upfile’][‘tmp_name’],$distination .’/’.$newfile))
……

文件重命名的防护效果良好,避免了针对后缀名的各种绕过。上面的代码利用md5 (uniqid (microtime(7)))来生成随机文件名,如果没有这个步骤,会有两个隐患:%00截断及解析漏洞。因此,最好再配合对文件名的随机数文件名生成方法,以提升防护效果。由于适用场景单一,目前基本上无有效有段进行绕过。

4.3.3 文件扩展名检测绕过方式

通过观察防护源码,可知黑名单/白名单机制归根结底为限制用户上传文件后缀名,避免被解析成Web的后缀名出现,这样就可以有效地保障Web服务器的安全,避免上传漏洞。

限制文件扩展名的方法归根结底为明确“限制/允许”条件,并且在这种防护手段下,攻击者无法控制重要参数或条件。因此,此种防护手段效果明显好于JS防护、MIME防护手段。但是也存在绕过方式,如尝试未被过滤的文件名,利用截断漏洞等实现木马上传。绕过思路为:构造非限制条件,以欺骗服务器、从而达到绕过的行为。

针对黑名单,有以下几种常用绕过手段。

(1)多重测试过滤文件名

参考黑名单的示例,其中针对有.php后缀名的文件禁止上传,但没有对其他格式做出限制。因此可尝试php4、php5、cer等后缀。此类后缀 不受黑名单的限制,同时中间件仍按照php等进行解析。例如,直接上传php文件,会报类型错误,尝试用php5后缀,则上传成功。

(2)判断是否存在大小写绕过

中间件会区分文件名的大小写,但操作系统并不区分文件名后缀的大小写。因此,如果黑名单写得不完全,攻击者则极易利用大小写进行绕过。假设当前黑名单禁用php、PHP、PhP后缀名,而PHp、pHp等后缀名不在名单内。因此尝试上传pHp为后缀名的文件。

(3)特殊文件名构造(Windows下)

构造shell.php.shell.php_(此种命名方式在Windows下不允许,所以需HTTP代理修改,可用burpsuite抓包并手动在相关字段处添加“_”下划线),当上传文件的HTTP包到达Web服务器后,并在中间件进行缓存转存时,由于Windows不识别上述后缀机制,会自动去掉“.”和“_”等特殊符号,从而使攻击者可绕过黑名单防护规则。

(4)%00截断

此绕过方式利用的是C语言的终止符特征,当C语言在执行过程中遇到%00,%00会被当成终止符,因此程序会自动截断后续信息,仅保留%00之前的内容。此漏洞仅存在于PHP5.3.7之前的版本,如shell.php .jpg。在上传页面进行转存时,之前文件名中的空格会被当成终止符,导致空格之后的内容被忽略。因此,最终文件名会变为shell.php,从而绕过了文件后缀名检测。

相比于黑名单的防护手段,白名单限制更加严格,非允许的后缀名一律拒绝上传,所以在黑名单中常用的修改大小写绕过手段、多类型后缀名绕过手段等,由于都无法满足白名单的过滤规则,因此都会被过滤。只有如下两种方式可以绕过防护机制:

  • 特殊文件名构造
  • 0x00截断

虽然以上方法都是利用系统缺陷实现的。在现实中,如果Web中间件版本过低,也会存在解析漏洞等情况,这样攻击者就能对后缀名检测实现更多的绕过方式。

4.4 服务器端文件内容检测及绕过

这类检测方法相对于上述检测方法来说更为严格。它通过检测文件内容来判断上传文件是否合法。但由于防护手段严格,允许的内容也就更加单一,这里针对图片上传功能进行防护分析。

4.4.1 文件内容检测防护思路

对文件内容的检测主要有以下三种方法:

  • 通过检测上传文件的文件头来判断当前文件格式
  • 调用API或函数对文件进行加载测试,常见的是图像二次渲染
  • 检测上传文件是否为图像文件内容

在上传漏洞的防护过程中,在文件上传时检测内容是一项非常有效的防护措施。防护手段可包含检测文件的文件头及内容图像格式是否合法,或者在显示过程中调用函数进行二次渲染,导致木马等非图像代码由于无法渲染成像素而被丢弃,从而达到防护的效果。但需注意的是,针对图像二次渲染会给服务器带来额外的性能开销。因此需要根据业务防护需求来寻找性能开销与安全要求之间的平衡点。

4.4.2 文件内容检测防护代码

(1)文件头判断

读取文件开头部分的数个字节,判断文件头与文件类型是否匹配。通常情况下,通过判断前10个字节基本上就能判断出一个文件的真实类型。

在HTML上传表单中,先获取文件名,并对文件后缀名根据原有规则进行匹配。如果确认是合法文件名,则认为可信,即进入后续流程。此种方法完全为服务器端信息判断,在此过程中,客户提交的信息无法修改,因此防护效果要明显优于MIME类型验证机制。

如下是基于上传的文件头判断防护代码,其中array均为常见文件格式的文件头。

function getTypeList()
{
	return array(array("FFD8FFE0","jpg"),
	array(array("89504E47","png"),
	array(array("47494638","gif"),
	array(array("49492A00","tif"),
	array(array("424D","bmp"),
	array(array("41433130","dwg"),
	array(array("38425053","psd"),
	array(array("7B5C727466","rtf"),
	array(array("3C3F786D6C","xml"),
	array(array("68746D6C3E","html"),
	array(array("44656C69766572792D646174","eml"),
	array(array("CFAD12FEC5FD746F","dbx"),
	array(array("2142444E","pst"),
	array(array("D0CF11E0","xls/doc"),
	array(array("5374616E64617264204A","mdb"),
	array(array("FF575043","wpd"),
	array(array("252150532D41646F6265","eps/ps"),
	array("255044462D312E","pdf"), 
	array("E3828596","pwl"), 
	array("504B0304","zip"), 
	array("52617221","rar"), 
	array("57415645","wav"), 
	array("41564920","avi"), 
	array("2E7261FD","ram"), 
	array("2E524D46","rm"), 
	array("000001BA","mpg"), 
	array("000001B3","mpg"), 
	array("6D6F6F76","mov"), 
	array("3026B2758E66CF11","asf"), 
	array("4D546864","mid")); 
}
function checkFileType($fileName){
	$file = @fopen($fileName, "rb");
	$bin = fread($file, 5);   //只读5字节
	fclose($file);
	$typelist=getTypeList();

	foreach($typeList as $v)
	{
		$blen=strlen(pack("H*",$v[0]));
		$tbin=substr($bin,0,intval($blen));
		if(strtolower($v[0])==strtolower(array_shift(unpack("H*",$tbin))))
		{
			return $v[1];
		}
	}
		return 'error';
}
$upfile=$_FILES["upfile"];
$name=$upfile["name"];
$type=$upfile["type"];
$size=$upfile["size"];
$tmp_name=$upfile["tmp_name"];
	$distination = '/var/www/html/upload_04/file'.$name;
	$echo checkFileType($upfile['tmp_name']);
move_uploaded_file($upfile['tmp_name'],$distination);

(2)文件加载检测中的图像二次渲染

关键函数imagecreatefromjpeg从jpeg生成新的图片(类似的还有imagecreatefromgif、imagecreatefrompng等),这样就导致在二次渲染过程中,插入的木马无法渲染成像素,因此在渲染过程中被丢弃,进而使木马执行失效。

防护关键代码如下:

function newimage($nw,$nh,$source,$stype,$dest)
{
	$size = getimagesize($source);
	$w = $size[0];
	$h = $size[1];
	
	switch($stype)
	{
		case 'gif':
		$simg = imagecreatefromgif($source);
		break;
		case 'jpg':
		$simg = imagecreatefromjpeg($source);
		break;
		case 'png':
		$simg = imagecreatefrompng($source);
		break;
	}
	$dimg = imagecreatetruecolor($nw,$nh);
	$wm = $w/$nw;
	$hm = $h/$nh;
	$h_height = $nh/2;
	$w_height = $nw/2;
	
	if($w>$h)
	{//图像宽度大于高度的情况
		$adjusted_width = $w/$hm;
		$half_width = $adjusted_width/2;
		$int_width = $half_width - $w_height;
imagecopyresampled($dimg,$simg,-$int_width,0,0,0,$nw,$adjusted_width,$nh,$w,$h);//重采样拷贝部分图像并调整大小
	}
	else if(($w<$h||$w==$h))
	{
		$adjusted_height = $h/$wm;
		$half_height = $adjusted_height/2;
		$int_height = $half_height - $h_height;
imagecopyresampled($dimg,$simg,0,-$int_height,0,0,$nw,$adjusted_height,$w,$h);
	}
	else
	{
		imagecopyresampled($dimg,$simg,0,0,0,0,$nh,$nw,$w,$h);
	}
		imagejpeg($dimg,$dest,100);//输出新生成的图像到指定位置
}

(3)图像内容检测

可利用PHP中的getimagesize()函数实现。getimagesize()函数可获取目标图片的高度和宽度的像素值,再与本地获取到的图片信息进行对比,如果相同,则进行保存,不同,则放弃代码执行。防护代码如下:

$file_name = $_FILES[‘upfile’][‘tmp_name’];
print_r(getimagesize($file_name));
$allow_ext = array(‘image/png’,’image/gif’,’image/jpeg’,’image/bmp’);
$img_arr = getimagesize($file_name);
$file_ext = $img_arr[‘mime’];
if(in_array($file_ext,$allow_ext)){
if(move_iploaded_file($_FILES[‘upfile’][‘tmp_name’],$uploaddir . ‘/’ . $_FILES[‘upfile’][‘name’]))
{
echo ‘文件上传成功,保存于:’ . $uploaddir . $_FILES[‘upfile’][‘name’] . “\n”;
}

4.4.3 文件内容检测绕过方式

针对文件内容的检测方式,只有在文件开头添加所允许文件类型对应的文件头,方可绕过现有防护措施。下面介绍一些常用手段。

(1)文件头检测

修改文件头,对前20字节进行替换,后面再插入一句话木马,即可实现对文件内容检测的绕过。使用时先在要上传的文件的所有内容前添加GIF98a,Web系统可判断当前文件为gif类型,需要注意的是,在实际中仅使用这种方法很多时候不能成功,因为上传功能还检测了后缀名、MIME等。因此若仅仅是针对文件内容检测,可采用这种方法进行尝试。

(2)文件二次渲染(极难)

①基于数据二义性,即让数据既是图像数据也包含一句话木马。

②对文件加载器进行溢出攻击。

这里需要注意的是,如果仅仅对.php文件添加上述文件头,并不一定会控制MIME的生成值。因为在不同的浏览器下,针对文件生成MIME时会有不同的情况。IE浏览器默认会根据文件头确定MIME值。但是Chrome、Firefox则仍以后缀名方式进行判断。

4.5 上传流程安全防护总结

攻击者利用上传功能实现的主要目标是:

  • 上传木马
  • 让木马按照Web格式进行解析

因此,在防护手段上,系统设计者在设计之初,考虑到系统性能问题,无法对每个上传的内容进行检查。这是因为多数上传的文件内容过于庞大,如果贸然对文件内容进行完全检查,则要消耗大量的系统资源,同时对系统速度造成极大影响,导致用户体验下降。因此有效的防护手段就是避免木马按照既定格式进行上传。可用的思路有:
(1)限制高危扩展名上传

  • 利用黑白名单确定后缀名是否合法
  • 根据应用特点重新对上传文件进行后缀重命名

(2)限制高位文件内容出现

  • 利用内容检索来检测是否存在非正常内容
  • 确认图片格式与上传文件内容是否对应
  • 在图像加载时重新渲染,避免非图像内容出现

不同于XSS攻击与SQL注入攻击,在上传过程中,在每个步骤上均可开展防护,根本目标是避免木马在服务器端执行,这也就是上传攻击的防护初衷。

5. 文件解析攻击

上传漏洞的危害非常严重,因此在业务层面应尽可能添加防护手段,以实现对上传功能的保护。但是在部分低版本中间件中存在解析漏洞,导致虽然后缀名合法,但由于解析漏洞造成对上传文件的解析错误,导致木马可执行。

5.1 .htaccess攻击

Apache中允许对站点同时解析,并且提供给类型机制实现不同站点之间的个性化配置功能,.htaccess即为其中的一项。.htaccess可配置解析目标,比如如下代码,允许包含字符“XXE”的文件以PHP方式执行。

<FilesMatch “XXE”>
SetHander application/x-httpd-php
</FilesMatch>

这样导致的问题是:虽然用户端做了大量的防护功能,但是可利用虚拟目录功能,实现中间件层的解析欺骗,导致木马可以非正常后缀名进行执行。不过,实际过程中可利用的场景非常少,主要是由于.htaccess文件需放在当前Web目录下面。攻击者如果能够在当前Web目录下插入任意的文件,其实攻击者已经获得当前系统的webshell,也就没有必要利用这种方式进行攻击。

在早期以黑白名单为主流防护技术的时期,攻击者经常在获得当前系统的webshell之后,会修改.htaccess文件内容,添加未知后缀以php方式执行。这样就算当前的webshell丢失,也可在下次攻击时直接利用未知文件名重新快速获得webshell,这也算另一种“后门”的效果。

5.2 Web服务器解析漏洞攻击

解析漏洞可以说是Web安全中比较“古老”的漏洞类型,绝大多数解析漏洞都是中间件的版本过低造成的。由于中间件在判断文件类型时,存在判断机制的问题,导致在实际解析过程中并不会按照既定的后缀名进行解析。攻击者针对这种情况,在文件名构造上进行特殊设计,导致中间件在判断此类特殊文件名时触发解析漏洞,实际解析后的文件与文件名并不相同(.jpg文件会被当做.php文件执行)。解析漏洞的危害在于Web应用防护方面防护看似非常到位,但仍存在安全隐患。目前主流中间件在老版本中均存在解析漏洞。

以下为常见的存在解析漏洞的中间件及对应特征。

5.2.1 Apache解析漏洞攻击

Apache中间件早期版本在后缀解析test.php.x1.x2.x3时,Apache将从右至左开始判断后缀,若x3为非可识别后缀,再判断x2,直到找到可识别后缀为止,然后将该可识别后缀进行解析。

解析漏洞的危害在于对以上的文件上传防护机制都可以进行绕过。当然,存在解析漏洞的中间件版本都比较老,新版本不存在上述问题。因此要解决解析漏洞的问题,简单有效的方法就是升级中间件版本。

另外有一点需要注意,很多人会使用集成环境来部署Web服务器。集成环境部署简易、使用方便,因此应用广泛。但需要注意的是,集成环境的软件版本并不是中间件的版本。因此从安全角度考虑,需要严格检查集成环境中的Apache及PHP版本,避免出现解析漏洞或截断漏洞等。下面是一些存在解析漏洞的集成环境版本及中间件对应版本信息,用以核对当前服务器安全状态或进行漏洞测试:

  • WampServer2.0 All Version(WampServer2.0i/Apache 2.2.11)
  • WampServer2.1 All Version(WampServer2.1e-x32/Apache 2.2.17)
  • Wamp5 All Version(Wamp5_1.7.4/Apache 2.2.6)
  • AppServ 2.4 All Version(AppServ -2.4.9/Apache 2.0.59)
  • AppServ 2.5 All Version(AppServ -2.5.10/Apache 2.2.8)
  • AppServ 2.6 All Version(AppServ -2.6.0/Apache 2.2.8)

5.2.2 IIS解析漏洞攻击

IIS作为Windows环境中的中间件工具,使用非常广泛,目前最新IIS版本为10.早起的IIS版本中存在解析漏洞,主要涉及版本如下:

1)IIS6.0

随着Windows server 2003的停止维护,IIS6.0的利用范围也在大幅度缩小。目前,新版的IIS中已不存在解析漏洞。IIS6.0在解析asp格式的时候有两个解析漏洞:

①如果目录名包含“.asp”字符串,那么这个目录下所有的文件都会按照asp格式解析。例如,对于/xx.asp/xx.jpg(xx.jpg可替换为任意文本文件),IIS6.0会将xx.jpg解析为.asp文件。

②只要文件名中含有“.asp”,就会优先按照asp来解析。例如,对于/xx.asp;.jpg(此处需抓包修改文件名),IIS6.0都会把此类后缀文件成功解析为asp文件。

IIS6.0默认的可执行文件处理asp还包括以下三种:/xx.asa、/xx.cer、/xx.cdx。(在IIS默认配置中,这几个后缀默认由asp.dll来解析,所以执行权限和.asp一模一样,可在配置中自行删除该后缀,以免出现安全隐患。)

2)IIS7.0/IIS7.5

IIS7.0/7.5存在的解析漏洞利用场景比IIS6.0要苛刻,主要是在对php解析时存在类似于nginx的解析漏洞,并且需要处于Fast-CGI开启状态。在漏洞利用方式上,只要在任意文件名的URL后追加字符串“/任意文件名.php”,那么当前文件就会按照php的方式解析。

利用方法如下:将一张图和一个写入后门代码的文本文件进行合并,将恶意文本写入图片的二进制代码之后,避免破坏图片文件头和尾。

需要注意的是,IIS7.0/7.5下的解析漏洞是由于php-cgi本身的问题导致,与IIS自身并没有直接关系。

5.2.3 Nginx解析漏洞攻击

目前Nginx的解析漏洞利用方式与Apache、IIS基本一致。一个是对于任意文件名,在后面添加/任意文件名.php的解析漏洞,不如原本文件名是test.jpg,可以添加为test.jpg/x.php进行解析攻击。还有一种是针对低版本的Nginx,可以在任意文件名后面添加%00.php进行解析攻击。

下面几个版本都存在此问题:

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

任意文件名/任意文件名.php这个漏洞其实是出现自php-cgi的漏洞,与Nginx自身无关,这点与IIS 7.0/7.5与PHP配合使用时解析漏洞产生原理一样。

6. 总结

上传攻击是攻击者直接获取webshell的有效手段,因此在设计防护策略时要尽可能限制用户上传文件的类型,以及对用户上传文件的内容进行过滤。但总体来说,有效的防护手段仍是从业务设计角度出发,尽可能要求上传文件为单一的类型。除此之外,也不能忽视攻击者上传的木马效果,因为只要木马上传成功,就可以利用其他漏洞实现木马的执行,因此必须予以重视。

来源:freebuf.com 2020-10-22 15:24:25 by: magic24

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论