0x01漏洞概述
upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共21关,每一关都包含着不同上传方式。
0x02环境配置
在 Windows 下使用 phpstudy 搭建靶场环境,将靶场放置到其 web 环境当中
靶场地址:https://github.com/c0ny1/upload-labs
成功访问说明靶场已经部署完成
思维导图如下:
0x03前端验证
Pass-01
场景分析
直接上传PHP文件浏览器会提示文件类型不正确,这多半就是前端校验
function checkFile() { var file = document.getElementsByName('upload_file')[0].value; if (file == null || file == "") { alert("请选择要上传的文件!"); return false; } //定义允许上传的文件类型 var allow_ext = ".jpg|.png|.gif"; //提取上传文件的类型 var ext_name = file.substring(file.lastIndexOf(".")); //判断上传文件类型是否允许上传 if (allow_ext.indexOf(ext_name + "|") == -1) { var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name; alert(errMsg); return false; } }
从上可知 JS 中定义白名单,白名单里包含.jpg、.png、.gif。前端验证绕过有三个思路,一是满足 JavaScript 规定的条件,通过抓包修改文件名;二是让用于验证的 JavaScript 代码不生效;三是修改 JavaScript 满足上传条件。
抓包绕过
首先将文件名phpinfo.php后缀修改为.png
mv phpinfo.php phpinfo.png
截取上传数据包并后缀名修改为php
成功访问phpinfo.php
禁用JS
使用浏览器插件Quick Javascript Switcher禁用JavaScript
成功访问phpinfo.php
调试JS
在浏览器中选择审查元素并在以下位置中设置断点
创建top/172.16.117.135/upload/Pass-01/index.php目录文件并在其中添加后缀名
var allow_ext = ".jpg|.png|.gif|.php";
在Sources中找到Overrides选择本地目录进行覆盖并保存
刷新网站后再次上传即可直接上传成功
删除JS
复制站点源代码为test.html并从中删除 JavaScript 限制代码
在上传点修改action路径,指定图片上传脚本
<form action="http://172.16.117.135/upload/Pass-01/index.php" enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
选择文件点击上传
成功访问上传文件
0x04后端验证
Pass-02
场景分析
MIME 是什么呢?
MIME(媒体类型)是一种标准,主要用来表示文档、文件或字节流的性质和格式。它的组成结构非常简单,由类型与子类型两个字符串中间用'/'分隔而组成。不允许空格存在。type 表示可以被分多个子类的独立类别。subtype 表示细分后的每个类型。
type/subtype
常见的 MIME 类型包含如下:
application/octet-stream #应用程序的默认值 text/plain #文本类型的默认值 text/html #html类型的默认值 text/css #CSS类型默认值 text/javascript #JavaScript类型的默认值 image/png #png图片的默认值
$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.'文件夹不存在,请手工创建!'; } }
这是一个后端校验的 PHP 代码,从上可知其中定义了 MIME 白名单,白名单里包含了image/jpeg、image/png、image/gif,脚本会检测上传数据包的 MIME 类型是否在白名单当中,如果不在白名单之内则无法上传。通过修改上传数据包的 MIME 类型可绕过限制,文件后缀名并没有因此发生改变,我们能直接访问木马文件。
修改MIME
上传php 文件后截取数据包将Content-Type类型修改为图片类型image/png
成功访问上传文件
Pass-03
场景分析
$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 . '文件夹不存在,请手工创建!'; } }
这是一个后端校验的 PHP 代码,从上可知其中定义了文件后缀名黑名单,黑名单里包含.asp、.aspx、.php、.jsp,脚本会检测后缀名是否在黑名单当中,如果在黑名单则无法上传。虽然在黑名单中禁止了典型的脚本后缀名,但是与典型的脚本后缀同义的后缀名还有许多,比如php5、pht、phtml、php4等后缀名都能解析为php脚本,而cer、asa、cdx等后缀名都能解析为asp脚本。因此通过修改同义后缀名可绕过限制。
需要注意的是在默认情况下 php 的同义后缀名是无法解析的。
修改同义后缀名
上传 php 文件后截取数据包将后缀名类型修改为php5
在默认情况下.php5无法被正常解析
解决方法是修改 Apache 默认配置文件httpd.conf并重启
AddType application/x-httpd-php .php .phtml .php5
AllowOverride All
成功访问上传文件
Pass-04
场景分析
$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",".ini"); $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 . '文件夹不存在,请手工创建!'; } }
这是一个后端校验的 PHP 代码,从上可知其中定义了文件后缀名黑名单,黑名单里几乎包含了所有可解析的后缀名以及.ini,脚本会检测文件后缀名是否在黑名单当中,如果在黑名单当中则无法上传。但黑名单当中忽略了.htaccess文件。
什么是.htaccess呢?
.htaccess文件可用于覆盖 Apache 的默认配置文件httpd.conf修改解析格式来绕过黑名单。它提供了针对目录改变配置的方法,在特定的文档目录中放置该文件可作用此目录以及所有子目录。而.htaccess生效的前提与上一题类似,需要在 Apache 配置文件httpd.conf配置参数AllowOverride为All,因此我们可通过上传定制的.htaccess绕过限制
上传.htaccess
编写.htaccess文件并添加 php 解析,使所有带有 mac 的文件都能解析为 php
<FilesMatch "mac"> Sethandler application/x-httpd-php </FilesMatch>
上传时又遇到一个问题:.htaccess无法被选择。我们可将其重命名为1.htaccess后通过 BurpSuite 抓包修改回.htaccess
然后上传带有 php 代码的mac.png
成功访问上传文件
在实战中需要注意的是如果遇到文件名被重命名就不能使用该方法,因为即使重命名为1.htaccess也无法生效,切记
Pass-05
场景分析
访问readme.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 . '文件夹不存在,请手工创建!'; } }
这是一个后端校验的 PHP 代码,从上可知其中定义了文件后缀名黑名单,黑名单里几乎包含了所有可解析的后缀名和.htaccess,脚本会检测文件后缀名是否在黑名单当中,如果在黑名单当中则无法上传。但黑名单当中忽略了.user.ini文件。
什么是.user.ini呢?
.user.ini是 php 中一种配置文件,众所周知当服务器以fastcgi运行脚本程序 php 时,php.ini是 php 的配置文件,它能对文件解析、导入扩展等进行个性化配置,而.user.ini与php.ini类似,它相当于一个用户自定义的php.ini,但不能修改任意php.ini中的属性值,php.ini的属性设置可分为四大类
PHP_INI_USER #可在用户脚本(如ini_set())、Windows 注册表(PHP 5.3及以上)、.user.ini中设置 PHP_INI_PERDIR #可在php.ini、.htaccess、httpd.conf中设置 PHP_INI_SYSTEM #可在php.ini、httpd.conf中设置 PHP_INI_ALL #可在任何地方设置
从以上分类可知,只要不是PHP_INI_SYSTEM模式下的属性,均可在.user.ini中设置。auto_append_file以及auto_prepend_file均能自动包含文件且属于PHP_INI_PERDIR模式,通过利用这两个属性可上传.user.ini加载文件可完成文件包含。因此我们可通过上传定制的.user.ini绕过限制
上传.user.ini
编写.user.ini文件并添加 php 解析,使名为 mac.gif 的文件解析为 php
auto_prepend_file=mac.gif
将.user.ini重命名为1.user.ini后选择上传,通过 BurpSuite 抓包修改文件名回.user.ini
上传带有 php 代码的mac.gif,这时所有的 php 文件都会包含该图片
访问readme.php即可完成 php 解析