文件上传漏洞原理与绕过总结
文件上传漏洞
漏洞原理
先看一下一般文件上传的功能是怎么实现的
前端校验
1.创建表单,用于上传文件
<div id="upload">
<form action="upload_file.php" method="post" enctype="multipart/form-data" onsubmit="return CheckFile()">
<label for="file">头像:</label>
<input type="file" name="file" id="file">
<input type="submit" name="submit" value="提交">
</form>
</div>
2.js代码校验文件
// 以下JS代码功能为检查文件后缀名合法性
function CheckFile(){
var file = document.getElementById('file').value;
if(file == "" || file == null) {
alert('请选择您要上传的文件!');
return false;
}
// 定义文件上传的扩展名
var allow_ext = ".jpg|.png|.gif|.jpeg|.jiff";
// 提取上传文件的扩展名
var ext_name = file.substring(file.lastIndexOf("."));
// 判断上传文件是否符合要求
if (allow_ext.indexOf(ext_name) == -1){
var msg = "请输入" + allow_ext + "类型的文件!";
alert(msg);
return false;
}
}
/**
Extension:
substring(start, end):截取字符串,参数为下标;只填一个参数代表从该下标开始到结束
indexOf():返回参数所在的下标位置,若目标不存在则返回-1
lastIndexOf():返回该参数最后一个下标位置
*/
3.后端上传功能实现 upload_file.php
<?php
if($_FILES['file']['error'] > 0){
echo $_FILES['file']['error'];
}
/*
echo "上传文件名: " . $_FILES["file"]["name"] . "<br>";
echo "文件类型: " . $_FILES["file"]["type"] . "<br>";
echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB<br>";
echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"];
*/
// 保存上传的文件
// 判断文件是否存在
if (file_exists("upload/" . $_FILES['file']['name'])){
echo "文件已存在";
}
else{
// 将文件保存到指定目录
$filename = "upload/" . date("Ymd_His.") . end(explode('.', $_FILES['file']['name']));
// move_uploaded_file(file,newloc) file:要移动的文件 newloc:文件的新位置。
move_uploaded_file($_FILES['file']['tmp_name'], $filename) or die('上传失败');
}
echo "<script>alert('上传成功!')</script>";
echo "<script>location.href='./list.php'</script>";
?>
后端校验
// 判断后缀名
$allowExts = array("jpg", "jpeg", "png", "gif");
$ext_name = end(explode('.', $_FILES['file']['name']));
if (!in_array($ext_name, $allowExts)){
echo "<script>alert('请上传 .jpg | .png | .jpeg | .png | .gif 类型的文件!')</script>";
echo "<script>location.href='list.php'</script>";
}
// 判断MIME类型
$allowFileTypes = array("image/jpeg", "image/jpg", "image/gif", "image/png");
if (!in_array($_FILES['file']['type'], $allowFileTypes)){
echo "<script>alert('请上传 .jpg | .png | .jpeg | .png | .gif 类型的文件!')</script>";
echo "<script>location.href='list.php'</script>";
}
绕过方式
前端过滤绕过
1.后缀名过滤
上传时的文件后缀为合法的,例如.png,抓包修改回.php
2.MIME文件类型过滤
-
使用F12禁用或修改Javascript(可能会影响原本页面正常的功能)
-
使用BurpSuite抓包修改Content-Type字段值和文件后缀名
3.禁用js
禁止js可能会导致网站功能无法正常使用
黑名单绕过
1.等价扩展名
语言 | 等价扩展名 |
---|---|
asp | asa, cer, cdx |
aspx | ashx, asmx, ascx |
php | php2 php3 php4 phps phtml |
jsp | jspx jspf |
利用前提:使用php2 phtml等需要配置文件中存在解析类型
2.双写绕过
关键词过滤置空时,可以尝试双写绕过,例如SELSELECTECT
3.大小写绕过
可能只过滤了小写/大写的关键词,SeLeCT
4.点绕过
Windows中不允许文件名末尾是.
,上传文件名改为shell.php.
绕过过滤,写入时windows会自动将末尾的.
去除
5.空格绕过(仅Windows环境下)
同上,windows会将文件名末尾空格去除
6..htaccess文件
绕过
利用前提:
AllowOverride = All
,mod_rewrite
模块开启
.htaccess中写入
<FilesMatch "\.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
上传.htaccess文件,再上传.jpg
文件就会被当作php执行
7.中间件解析机制绕过
Apache
在Apache解析机制是当遇到无法识别的后缀名是,会依次取前一个后缀名。
例如上传shell.php.jjj,.jjj后缀名无法解析,因此shell.php.jjj会被当做shell.php解析
Nginx
在上传的图片马后添加/xx.php
,例如shell.jpg/xx.php
,上传的图片马就会被当作php执行
8.::$DATA
绕过(仅Windows环境下)
使用::$DATA
不会检测其后缀名,且保持::$DATA
之前的文件名。例如:shell.php::$DATA
会被解析为shell.php
白名单绕过
1.%00截断
利用条件:
php < 5.3.4,magic_quotes_gpc=off
,上传路径可控
例如上传shell.jpg,在上传路径/upload/后加上shell.php%00,最终会被处理成/upload/shell.php/%00.jpg,%00是空字符,后面的内容会被丢弃
2.图片马
有文件包含漏洞时,制作图片马上传(详见文末),然后通过文件包含漏洞连接shell
有些站点中会将图片二次渲染,可能会导致写入的一句话被破坏,可以将渲染后的图片下载下来,对比上传前后的图片十六进制数据,在相同即没有被修改的部分插入一句话
3.条件竞争绕过
服务器处理逻辑有问题,例如如下代码
$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 = '上传出错!';
}
}
问题在于,先将文件上传到服务器后,才进行白名单判断、移动、重命名等相关操作,最后再删除源文件。
因此只要上传路径可知,就可以利用文件上传到服务器还未进行操作的这个间隙,访问到我们上传的原始文件
漏洞修复
1.前后端都要校验MIME类型和后缀名
2.后缀名都要先转换为小写再校验
3.使用白名单限制
4.去掉文件名前后的空格等特殊字符
5.图片文件使用二次渲染并另存
6.对上传的文件要先重命名然后再移动,避免条件竞争
7.上传文件目录的权限设置为不可执行
图片马制作
1.文本方式或十六进制打开图片,直接插入代码
2.cmd中输入如下命令
copy 1.jpg/b + 2.php/a shell.php
3.在shell.php中添加文件头、文件尾
常见图片文件头文件尾:
文件类型 | 后缀 | 文件头 | 文件尾 | 标志 |
---|---|---|---|---|
JPEG | .jpg/.jpeg | FFD8FF | FFD9 | JFIF |
PNG | .png | 89504E47 | AE426082 | PNG IEND IHDR |
GIF | .gif | 47494638 | 003B | GIT9a |
TIFF | .tif/.tiff | 49492A00 | 4D4D2A00 | - II MM |