一、文件上传简介
“文件上传”本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。
文件上传后导致的常见安全问题一般有:
1)上传文件是Web脚本语言,服务器的Web容器解释并执行了用户上传的脚本,导致代码执行。
2)上传文件是Flash的策略文件crossdomain.xml,黑客用以控制Flash在该域下的行为(其他通过类似方式控制策略文件的情况类似);
3)上传文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行。
4)上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。
除此之外,还有一些不常见的利用方法:
比如将上传文件作为一个入口,溢出服务器的后台处理程序,如图片解析模块;
或者上传一个合法的文本文件,其内容包含了PHP脚本,再通过"本地文件包含漏洞(Local File Include)"执行此脚本;等等。
要完成这个攻击,要满足以下几个条件:
首先,上传的文件能够被Web容器解释执行。所以文件上传后所在的目录要是Web容器所覆盖到的路径。
其次,用户能够从Web上访问这个文件。如果文件上传了,但用户无法通过Web访问,或者无法得到Web容器解释这个脚本,那么也不能称之为漏洞。
最后,用户上传的文件若被安全检查、格式化、图片压缩等功能改变了内容,则也可能导致攻击不成功。
二、环境介绍
我使用的是0.1版本。环境为windows/apache/php5.2.17
https://github.com/c0ny1/upload-labs/releases
三、php中正常的文件上传过程解析:
这里以upload-labs的Pass01为例子解释一下正常的上传过程,抓包上传1.jpg,修改为1.php
前端的表单代码为
//form表单中可以定义enctype属性,该属性的含义是在发送到服务器之前应该如何对表单数据进行编码。默认的情况下,表单数据会编码为 “application/x-www-form-unlencoded”,此时在发送前编码所有字符。
//在使用文件上传时候,使用multipart/form-data, 此时不对字符编码。
上传成功后页面增加了一个img标签
随后浏览器会自动下载1.php文件并展示
关注一下请求头:
POST /Pass-01/index.php //因为没有指定action的URL,默认传递给index.php
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1V7SB71Mmd9ulCM2
//在网络请求中,常用的Content-Type有如下:text/html, text/plain, text/css, text/javascript, image/jpeg, image/png, image/gif, application/x-www-form-urlencoded, multipart/form-data, application/json, application/xml 等。
//这里的multipart/form-data会生成一个boundary字符串来分割请求头与请求体
------WebKitFormBoundary1V7SB71Mmd9ulCM2
Content-Disposition: form-data; name="upload_file"; filename="1.jpg"
Content-Type: image/jpeg #这是浏览器根据后缀名自动填的
------WebKitFormBoundary1V7SB71Mmd9ulCM2
Content-Disposition: form-data; name="submit"
涓婁紶
------WebKitFormBoundary1V7SB71Mmd9ulCM2--
注意到前面请求中的:
Content-Disposition: form-data; name=“upload_file”; filename=“1.jpg”
Content-Type: image/jpeg
随后我们可以在php文件中使用name="upload_file"来访问文件
$_FILES['upload_file']['tmp_name'] 是临时存储路径
$_FILES['upload_file']['name'] 是"1.jpg" // 注意引用格式是$_FILES[name]['name']=filename
$_FILES['upload_file']['size'] 是文件大小
$_FILES['upload_file']['type'] 是"image/jpeg"
当web页面通过http将一个文件上传到server时,会在/tmp中产生临时文件,然后程序中会用move_uploaded_file ( $_FILES [“Filedata”] [“tmp_name”], $filename );将文件拷到所需要存放该文件的目录下。
接着来看后台php处理代码:
index.php处理上传部分的源码
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$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 = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
四、绕过文件上传检查功能总结:
1.客户端校验,Pass01
2.服务端MIME类型(content-type)字段校验 Pass02
这两个都可以利用burp抓包改包绕过。
3.黑名单机制:
Pass03:使用畸形后缀如php5绕过
Pass04:上传.htaccess。这个文件名没有被重命名,不知道为什么。这样所有文件都会解析为php,然后再上传图片马,就可以解析。这个是Apache的漏洞。
Pass05:大小写绕过
如果是黑名单机制,还可以利用windows特性,windows系统在生成文件时会自动去掉不符合规则符号后面的内容,最后生成文件7.php,直接连接
pass06:抓包 后缀名加空绕过
pass07:抓包 后缀名加"."绕过。
pass08:抓包 后缀名中加”::$DATA”绕过
windows下还可以通过上传test.php:1.jpg文件名来绕过后缀检查
09-10:将filename加入最终路径会带来极大风险
例如$img_path = UPLOAD_PATH.'/'.$file_name;
这样的代码很危险
pass09:路径拼接的是处理后的文件名,于是构造info.php. . (点+空格+点),经过处理后,文件名变成9.php.,即可绕过。经测试,9.php.aaa.或者9.php.aaa也可以(这是利用Apache另一解析漏洞)
pass10:过滤存在问题,使用双写php后缀绕过单次过滤
$file_name = str_ireplace($deny_ext,"", $file_name); //只过滤一次php
4.相比黑名单,白名单方法更安全,11和12演示了如何在白名单的情况下使用00截断绕过:
受00截断影响的有操作系统和php函数,注意php中的00截断是有版本限制的。
由于11和12中我们可以控制上传路径:
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; //pass11
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; //pass12
pass11:
直接在URL中添加%00:
POST /Pass-11/index.php?save_path=…/upload/11.php%00 HTTP/1.1
如果path在非enctype=multipart/form-data的表单中或URL or Cookie中的时候,就可以直接写%00不需要进行URLdecode操作,让服务端对%00进行URL解码即可。
pass12:要在burp的二进制模式添加0x00,因为post不会像get对%00进行自动解码。
------WebKitFormBoundary30RdXSUEy745Z3V1
Content-Disposition: form-data; name=“save_path”
…/upload/12.php[0x00]
00截断的原理可以参考http://www.admintony.com/%E5%85%B3%E4%BA%8E%E4%B8%8A%E4%BC%A0%E4%B8%AD%E7%9A%8400%E6%88%AA%E6%96%AD%E5%88%86%E6%9E%90.html
dir存放在URL或者Cookie中的话,在提交数据的时候,浏览器会对数据做一次编码操作,而到服务端,会对数据进行一次urldecode的操作,此时直接写%00(写二进制会错误返回),让服务端对%00进行URL解码即可。
dir存放在表单中时,文件上传的表单中有一个enctype的属性,并且enctype=“multipart/form-data” (此时浏览器不对表单中数据进行编码,服务端也不会解码),因此需要在数据包中直接插入十六进制0x00。
pass19 利用了move_uploaded_file()函数的00截断:
CVE-2015-2348利用,注意
$img_path = UPLOAD_PATH . '/' .$file_name;
move_uploaded_file($temp_file, $img_path)
img_path为“upload/19.php\00.jpg”,会上传成为文件19.php
其他:还有就是move_uploaded_file底层会调用tsrm_realpath函数导致,递归删除文件名最后的/.导致绕过了后缀名检测。黑名单下将文件名改为:1.php/. 也能成功绕过move_uploaded_file。
有个疑惑,这里不能看出到底是windows还是move_uploaded_file存在截断漏洞。有可能是操作系统创建文件时遇到’/0’字符,认为字符串结束了。于是只创建了一个名为shell.php的文件。也有可能是函数本身漏洞。
5.pass13-16都是上传图片马,都需要结合文件包含漏洞执行代码
pass13:打开文件,读取前两字节判断上传文件是否为图片
pass14:利用getimagesize($filename);函数判断文件是否为图片
pass15:使用exif_imagetype()检查是否为图片文件
13-15都可以将php代码附加在图片结尾绕过,只是检查方式不同。没太大区别。
pass16:二次渲染绕过,我只绕过了gif,另外两个还没有
https://xz.aliyun.com/t/2657
6.pass17-18是基于条件竞争执行代码。
条件竞争需不需要文件包含看具体代码,一般是move文件之后再删除,中间有一小段时间可以利用。
初始上传文件的后缀:
如果是先判断拓展名再move文件,就可以上传1.php。
pass17:
如果上传文件后缀为php,会依次执行以下代码
$file_name = $_FILES['upload_file']['name'];
$upload_file = UPLOAD_PATH . '/' . $file_name;
move_uploaded_file($temp_file, $upload_file);
unlink($upload_file);
pass18:和17类似,是先move再rename。
由于18题是先检查拓展名,再move,最后rename为其他随机名字。所以不能上传php文件,要利用文件包含。
我下的这个版本myupload.php的103行的目录名后要加上"/",不然图片会生成在index同级目录
操作步骤:选择没什么用的参数,Intrude从1-10000不断请求,按F5刷新
总结:$upload_file = UPLOAD_PATH . ‘/’ . $file_name;会导致极大的不安全
虽然是1-10000,但是最终生成的文件并没有那么多,也只有几十个。可能是处理文件需要时间。
7.pass20:是一道CTF题,通过传递数组绕过。属于逻辑漏洞,没有考虑数组第二个元素不存在的情况。
------WebKitFormBoundaryG9Bjj0lT7kSPj8Mb
Content-Disposition: form-data; name="upload_file"; filename="20.php"
Content-Type: image/jpeg
------WebKitFormBoundaryG9Bjj0lT7kSPj8Mb
Content-Disposition: form-data; name="save_name[0]"
20.php/
------WebKitFormBoundaryG9Bjj0lT7kSPj8Mb
Content-Disposition: form-data; name="save_name[2]"
jpg
------WebKitFormBoundaryG9Bjj0lT7kSPj8Mb
Content-Disposition: form-data; name="submit"
涓婁紶
------WebKitFormBoundaryG9Bjj0lT7kSPj8Mb--
最终文件名为“20.php/.” 因为move_uploaded_file底层会调用tsrm_realpath函数导致,递归删除文件名最后的/.
总结:其实初始上传的文件名并不重要,因为最后都可以抓包修改。不过为了方便绕过客户端js检测,可以命名为jpg后缀。
通关详细链接:
https://blog.csdn.net/vhkjhwbs/article/details/89740197
最后发一个在黑盒下测试的流程图
最后,这里的测试环境比较单一,有时间会总结一下不同服务器,不同版本的php对00截断的影响。