文件上传漏洞原理与绕过总结
文件上传漏洞
漏洞原理
先看一下一般文件上传的功能是怎么实现的
前端校验
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 |
本文作者:Mast1n's Blog
本文链接:https://www.cnblogs.com/Mast1n/p/upload.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步