文件上传漏洞总结
0x00 原理
文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷,而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马,病毒,恶意脚本或者WebShell等。“文件上传”本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。
0x01 漏洞案例演示
PS:通过自己搭建靶场的方式利用漏洞,可以更加熟练PHP、JS的使用,同时可以更好的理解漏洞产生的原因及如何防御
前端校验
1.创建表单,用于上传文件
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; }}// 需要在要提交的表单写入属性 onsubmit = return CheckFile();
Extension:
substring(start, end):截取字符串,参数为下标;只填一个参数代表从该下标开始到结束indexOf():返回参数所在的下标位置,若目标不存在则返回-1lastIndexOf():返回该参数最后一个下标位置
3.后端页面upload_file.php
0){ echo $_FILES['file']['error'];}/* echo "上传文件名: " . $_FILES["file"]["name"] . "
";echo "文件类型: " . $_FILES["file"]["type"] . "
";echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB
";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 "alert('上传成功!')";echo "location.href='./list.php'";?>
后端校验
// 判断后缀名$allowExts = array("jpg", "jpeg", "png", "gif");$ext_name = end(explode('.', $_FILES['file']['name']));if (!in_array($ext_name, $allowExts)){ echo "alert('请上传 .jpg | .png | .jpeg | .png | .gif 类型的文件!')"; echo "location.href='list.php'";}// 判断文件类型$allowFileTypes = array("image/jpeg", "image/jpg", "image/gif", "image/png");if (!in_array($_FILES['file']['type'], $allowFileTypes)){ echo "alert('请上传 .jpg | .png | .jpeg | .png | .gif 类型的文件!')"; echo "location.href='list.php'";}
0x02 绕过方式前端过滤绕过1.后缀名过滤2.MIME文件类型过滤
使用F12禁用或修改Javascript(可能会影响原本页面正常的功能)
使用BurpSuite抓包修改Content-Type字段值和文件后缀名
黑名单绕过1.等价扩展名
语言 等价扩展名 asp asa, cer, cdx aspx ashx, asmx, ascx php php2 php3 php4 phps phtml jsp jspx jspf
利用前提:使用php2 phtml等需要修改配置文件,添加解析类型
2.双写绕过
如pphphp,将php替换为空后剩下的字符也会组合成php
3.大小写绕过4.点绕过5.空格绕过(仅Windows环境下)6.点空格点绕过7..htaccess文件绕过
利用前提:
AllowOverride = All
,mod_rewrite
模块开启
在.htaccess中写入
SetHandler application/x-httpd-php
上传.htaccess文件,再上传其他文件即可被当作php执行
8.apache解析机制绕过
例如上传shell.php.jjj,jjj后缀名无法解析,因此shell.php.jjj会被当做shell.php解析
9.::$DATA绕过(仅Windows环境下)
使用::$DATA
不会检测其后缀名,且保持::$DATA
之前的文件名。例如:shell.php::$DATA
会被解析为shell.php
10.linux目录机制
linux中,每个目录下都有两个隐藏索引
白名单绕过1.%00截断
截断条件:
php版本<5.3.4且magic_quotes_gpc=off
,上传路径可控
例如上传shell.jpg,在上传路径/upload/后加上shell.php%00,最终会被处理成/upload/shell.php/.jpg,/后的内容被忽略
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.文本方式或十六进制打开图片,直接插入代码
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
0x03 漏洞防御
1.前后端都要校验MIME类型和后缀名
2.后缀名都要先转换为小写再校验
3.使用白名单限制
4.去掉文件名前后的空格等特殊字符
5.图片文件使用二次渲染并另存
6.对上传的文件要先重命名然后再移动,避免条件竞争
7.上传文件目录的权限设置为不可执行