21:WEB漏洞-文件上传之后端黑白名单绕过
知识点
文件上传常见验证:后缀名,类型,文件头等
1.后缀名:黑名单,白名单
- 黑名单:明确不让上传的格式后缀,比如asp,php,jsp,aspx,cgi,war等,但是黑名单易被绕过,比如上传php5,Phtml等
- 白名单:明确可以上传的格式后缀,比如jpg,png,zip,rar,gif等,推荐白名单。
2.文件类型:MIME信息
- content-type字段校验,可以通过抓包改包方式绕过
3.文件头:内容头信息
- 每种类型的文件都有自己固定的文件头信息,比如GIF89a是git图片的文件头信息,可以通过手动在脚本文件前面增加文件头的方式绕过。
4.windows特性
- windows下文件名不区分大小写,linux下文件名区分大小写
- windows下ADS流特性,导致上传文件xxx.php::$DATA = xxx.php
- windows下文件名结尾加入“.”、“空格”、“<”、“>”、“>>>”、“0x81-0xff”等字符,最终生成的文件均被windows忽略。
本课重点
案例:uploadlabs关卡分析
下载:https://github.com/c0ny1/upload-labs
- 案例1:$_FILES['upfile']访问文件的有关信息
- 案例2:Pass-02 MIME-Type验证
- 案例3:Pass-3 黑名单绕过 特殊解析后缀
- 案例4:Pass-4 .htaccess绕过
- 案例5:Pass-5 大小写绕过
- 案例6:Pass-6 后缀名空格绕过
- 案例7:Pass-7 点绕过
- 案例8:Pass-8 ::$DATA绕过
- 案例9:Pass-9 点+空格+点绕过(循环递归过滤)
- 案例10:Pass-10 双写绕过
- 案例11:Pass-11 %00截断 GET请求
- 案例12:Pass-12 %00截断 POST请求
案例1:PHP基础知识:若文件上传域的name属性值为upfile,则可以使用$_FILES['upfile']访问文件的有关信息。
- $_FILES['upfile']['name']; //客户端上传文件的原名称,不包含路径
- $_FILES['upfile']['type']; //上传文件的MIME类型
- $_FILES['upfile']['tmp_name']; //已上传文件在服务器端保存的临时文件名,包含路径
- $_FILES['upfile']['error']; //上传文件出现的错误号,为一个整数
- $_FILES['upfile']['size']; //已上传文件的大小,单位为字节
案例2:Pass-02 MIME-Type验证
MIME(multipurpose Internet mail extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
查看代码,系统校验了MIME-Type
$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.'文件夹不存在,请手工创建!'; } }
因此只要修改content-type值为符合条件的值即可绕过。
修改
上传成功。
附各类文件MIME_type对照表
{".3gp", "video/3gpp"}, {".apk", "application/vnd.android.package-archive"}, {".asf", "video/x-ms-asf"}, {".avi", "video/x-msvideo"}, {".bin", "application/octet-stream"}, {".bmp", "image/bmp"}, {".c", "text/plain"}, {".class", "application/octet-stream"}, {".conf", "text/plain"}, {".cpp", "text/plain"}, {".doc", "application/msword"}, {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {".xls", "application/vnd.ms-excel"}, {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {".exe", "application/octet-stream"}, {".gif", "image/gif"}, {".gtar", "application/x-gtar"}, {".gz", "application/x-gzip"}, {".h", "text/plain"}, {".htm", "text/html"}, {".html", "text/html"}, {".jar", "application/java-archive"}, {".java", "text/plain"}, {".jpeg", "image/jpeg"}, {".jpg", "image/jpeg"}, {".js", "application/x-javascript"}, {".log", "text/plain"}, {".m3u", "audio/x-mpegurl"}, {".m4a", "audio/mp4a-latm"}, {".m4b", "audio/mp4a-latm"}, {".m4p", "audio/mp4a-latm"}, {".m4u", "video/vnd.mpegurl"}, {".m4v", "video/x-m4v"}, {".mov", "video/quicktime"}, {".mp2", "audio/x-mpeg"}, {".mp3", "audio/x-mpeg"}, {".mp4", "video/mp4"}, {".mpc", "application/vnd.mpohun.certificate"}, {".mpe", "video/mpeg"}, {".mpeg", "video/mpeg"}, {".mpg", "video/mpeg"}, {".mpg4", "video/mp4"}, {".mpga", "audio/mpeg"}, {".msg", "application/vnd.ms-outlook"}, {".ogg", "audio/ogg"}, {".pdf", "application/pdf"}, {".png", "image/png"}, {".pps", "application/vnd.ms-powerpoint"}, {".ppt", "application/vnd.ms-powerpoint"}, {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, {".prop", "text/plain"}, {".rc", "text/plain"}, {".rmvb", "audio/x-pn-realaudio"}, {".rtf", "application/rtf"}, {".sh", "text/plain"}, {".tar", "application/x-tar"}, {".tgz", "application/x-compressed"}, {".txt", "text/plain"}, {".wav", "audio/x-wav"}, {".wma", "audio/x-ms-wma"}, {".wmv", "audio/x-ms-wmv"}, {".wps", "application/vnd.ms-works"}, {".xml", "text/plain"}, {".z", "application/x-compress"}, {".zip", "application/x-zip-compressed"}, {"", "*/*"}
案例3:Pass-3 黑名单绕过 特殊解析后缀
源码配置了黑名单,不允许上传.asp,.aspx,.php,.jsp后缀的文件
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array('.asp','.aspx','.php','.jsp'); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //收尾去空 if(!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file,$img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
但apache服务器能够使用php解析.phtml .php3 .php5
前提是apache的httpd.conf中有如下配置代码
AddType application/x-httpd-php .php .phtml .php3 .php5
因此可以上传.phtml .php3 .php5文件,绕过黑名单
案例4:Pass-4 .htaccess绕过
源码配置了黑名单,拒绝了几乎所有有问题的后缀名,除了.htaccess
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //收尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
.htaccess作为局部变量成功作用于当前目录下文件的两个条件(1.启用AllowOverride,2.开启mod_rewrite模块)
修改httpd.conf: 1、Allow Override All 2、LoadModule rewrite_module modules/mod_rewrite.so
本关正好符合,因此先上传一个.htaccess文件,内容如下:
<FilesMatch "hello"> setHandler application/x-httpd-php </FilesMatch>
作用是使当前目录下所有文件名包含“hello”字符串的文件当作php文件解析。
然后再上传一个hello.jpg文件,内容如下:
<?php phpinfo(); ?>
此时访问该文件web路径,服务器执行hello.jpg文件中的PHP代码。
htaccess上传漏洞前提条件:
- 1、apache服务器。
- 2、能够上传.htaccess文件,一般为黑名单限制。
- 3、AllowOverride All,默认配置为关闭None。
- 4、LoadModule rewrite_module modules/mod_rewrite.so #mod_rewrite模块为开启状态
- 5、上传目录具有可执行权限。
补充:.htaccess简介
- .htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
- .htaccess文件(或者"分布式配置文件")提供了针对每个目录改变配置的方法,即在一个特定的目录中放置一个包含指令的文件,其中的指令作用于此目录及其所有子目录。
- 启用.htaccess,需要修改httpd.conf,启用AllowOverride。一旦启用.htaccess,意味着允许用户自己修改服务器的配置,可能会导致某些意想不到的修改。安全起见,应该尽可能地避免使用.htaccess文件。
案例5:Pass-5 大小写绕过
源码相比于pass-4,过滤了.htaccess,但去掉了将后缀转换为小写,因此可以使用大小绕过。
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
可以上传.PHP文件,绕过黑名单。
案例6:Pass-6 后缀名空格绕过
源码相较于pass-4、pass-5,没有对后缀名进行去空,利用windows特性,会自动去掉后缀名中最后的空格,因此可以后缀名加空格绕过。
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = $_FILES['upload_file']['name']; $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file,$img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件不允许上传'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
可以在上传文件时,抓包,将文件后缀改为.php+空格,绕过黑名单。
原理是 服务器在校验黑名单时,校验的后缀名是.php+空格,由于.php+空格不在黑名单内,可以通过校验,而windows系统在保存文件时,会自动去掉后面的空格,因此文件最终保存在服务器上的后缀名为.php。(linux系统在保存文件时应该也会自动去除空格,可以自行测试一下?)
案例7:Pass-7 点绕过
源码相较于pass-4,没有删除文件名末尾的点,利用windows特性,会自动去掉后缀名中最后的”.”,可在后缀名中加”.”绕过。
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
案例8:Pass-8 ::$DATA绕过
源码相较于pass-4,没有对后缀名中的“::$DATA”进行过滤。在php+windows的情况下,如果文件名+“::$DATA”会把“::$DATA”之后的数据当成文件流处理,不会检测后缀名,且保持“::$DATA”之前的文件名。利用windows特性,可在后缀名后面加“::$DATA”绕过。
例如“phpinfo.php::$DATA” Windows会自动去掉末尾的“::$DATA”变成“phpinfo.php”。
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
使用burpsuite抓包在文件后缀加::$DATA绕过。
案例9:Pass-9 点+空格+点绕过(循环递归过滤)
源码相较于前几关,所有的过滤都有。貌似没有问题,但是所有的过滤仅一次,先删除文件名末尾的点(仅删除一次),然后再首尾去空,导致可以利用1.php+(点+空格+点)来绕过。
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
使用burpsuite抓包在文件后缀加“点+空格+点”绕过。
原理是1.php+(点+空格+点)上传时,
- 首先,删除文件名末尾的点,变成1.php+点+空格,
- 然后,通过strrchar函数来确认文件的后缀名为.php+点+空格,
- 接着,将文件的后缀名转换为小写、去除字符串::$DATA、首尾去空,变成.php+点,
- 最后,判断文件后缀名是否在黑名单内。由于“.php.”不在黑名单中,可以通过校验,而windows特性,保存文件时会自动去掉后缀名中最后的”.”,最终文件成功上传并保存为1.php。
案例10:Pass-10 双写绕过
黑名单过滤,将黑名单里的后缀名替换为空且只替换一次,因此可以用双写绕过
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_name = str_ireplace($deny_ext,"", $file_name); $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; }
使用burpsuite抓包将文件后缀改为.pphphp绕过。
案例11:Pass-11 %00截断
源码
$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类型文件!"; } }
白名单过滤,但$img_path是直接拼接而成,因此可以利用%00截断绕过。
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
截断条件:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态
案例12:Pass-12 %00截断
源码
$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 = $_POST['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类型文件!"; } }
源码相较于pass-11,save_path参数通过POST方式传递
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
还是利用%00截断,因为POST不会像GET对%00进行自动解码,所以需要在二进制中进行修改。
参考:
- https://blog.csdn.net/weixin_44677409/article/details/92799366
- https://www.cnblogs.com/adforce/archive/2012/11/23/2784664.html
- 等等