08_上传漏洞_初识_前后端绕过&靶场案例
1.初识上传漏洞
1.1 认识漏洞
许多网站会设计用户上传功能,例如大多数网站存在会员机制,会员可以上传图片作为自己的头像。这样的上传功能中往往会做限制(如只允许上传图片格式),试想,某些攻击者如果绕过了上传限制,通过上传功能将恶意脚本(远程控制等)发送至服务器,那么服务器资源将会遭受威胁。
可见,文件上传本身是一个常见功能,只有在设计不得当时才会是一个漏洞。因此,当系统存在文件上传功能时,就有一定的可能性存在上传漏洞。上传漏洞和先前介绍的SQL注入漏洞一样,都属于高危漏洞。绕过上传限制后,攻击者可以上传后门脚本进而获取服务器权限,内网权限等,这类漏洞的威胁等级也是极其高的。
简而言之,这类漏洞的利用方向就是绕过机制检测,上传自定义功能性文件(往往是一个脚本,脚本类型需配合前期指纹扫描进行)。
1.2 漏洞的利用思路
这一小节不进行详细描述,只是将利用思路整理成一个思维导图,后续操作时可以经常根据这张简要的思维导图寻找思路,本系列博文也将根据该思维导图展开。
2. 常规思路:客户端检测
【说明:本节内容将从开发角度进行上传漏洞的介绍,为了配合本章的学习,需要提前部署好靶场环境。
案例环境:phpstudy + upload-libs;
靶场地址:https://github.com/c0ny1/upload-labs。
这个地址获取的靶场不需要自行创建upload目录,直接将文件解压放到www目录下就能够使用。】
网页开发时常常会开发文件上传功能。例如会员中心中,用户可以上传图片作为自己的头像。开发者根据该功能的需求,设计编写检测机制只允许上传图片格式文件。常见的上传校验有两种:1.客户端(前端)检测;2.服务器端(后端)检测。本节将介绍客户端检测的原理和绕过的手段
2.1 Pass-01:客户端检测
前端代码中能够依据文件后缀名对文件上传实现一定的过滤。但写在前端的过滤,对于没有计算机基础的用户而言可以起到过滤作用,而对于专业技术人员而言,这种文件上传过滤显然是存在很大缺陷的。下面介绍一种可行的绕过方案。
在介绍案例前,先介绍本章靶场,案例选用upload-labs的Pass-01,为了对漏洞有一个更好的认识,我们从源码进行分析。在浏览器进行前端查看,发现是在前端的js代码进行文件过滤。代码如下:
<script type="text/javascript">
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;
}
}
</script>
简要分析代码…等等,这个过滤写在前端,那么思路就明确了。
- 思路一:前端代码是客户端执行,那么我们完全可以F12查看页面源码,复制代码到本地,改写或直接删除过滤代码。
- 思路二:浏览器存在禁用js脚本功能,一旦启用该开关,js脚本将失效,如果过滤是用js编写的,那么开关一开,过滤白给。但是这种方法也存在弊端,因为该开关针对的是所有js脚本的“无差别打击”,因此一旦开启很有可能影响正常的使用,因此该思路仅作备用,不推荐。
- 思路三:在能抓到包的情况下,采用工具抓包,修改对应参数进行绕过
本地文件修改
思路明确,那么我们该如何操作?首先,我们上传一个正常的文件,分析数据包,获取上传路径
:http://127.0.0.1/upload-labs/Pass-01/index.php
复制页面源代码到本地(后缀为html),修改或删除过滤代码,这里选择修改:假设我们需要上传的是php脚本
修改一:过滤函数(整段删除也可)
<script type="text/javascript">
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif|.php";//增加文件后缀.php
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
</script>
修改二:form(表单)标签
修改或添加action指向需要发送的地址:
<form action="http://127.0.0.1/upload-labs/Pass-01/index.php" enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
修改完后浏览器中打开,上传php脚本确定是否绕过成功:
点击上传,检查文件
脚本上传成功,客户端检测绕过成功!当然利用调试工具也能轻松实现,这里不加以展示。
3. 常规思路:服务器端黑名单绕过
先前内容中描述的客户端检测弊端相当明显:前端中的过滤用户可以自定义。某种意义上形同虚设,故而对于文件后缀的检测和过滤往往写在后端代码中,本章将根据upload-labs中的案例对每个知识点和思路进行剖析。
3.1 黑名单
黑名单是一种极为糟糕的过滤方式,其规定了多个系统认为不安全的后缀名,但凡上传文件是这几种文件类型的,都被判定为不合法文件,服务器拒绝接收。但是开发中难免有疏漏,某些文件后缀还是会被忽略,且在代码不够严谨的情况下可以通过多种方式进行绕过。
3.2 Pass-02 :MIME验证绕过
【说明】此方法仅在可以抓到包的情况下可用于绕过前端过滤
MIME类型用于设定某种扩展名文件的打开方式,具有该扩展名的文件被访问时,浏览器会自动指定应用程序打开。这种类型的绕过在upload-labs的pass-02中得到复现。过关方式如下
代码解析
$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.'文件夹不存在,请手工创建!';
}
}
这段过滤代码中,我们需要注意的是过滤函数中的条件
$_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')
这里是对MIME类型进行限制,只能够接收jpeg、png、gif三类文件类型,绕过思路也很简单,burp抓包修改content-type字段。
绕过方法
选择想要上传的木马脚本,打开burp的代理拦截功能:
点击上传,抓包分析,关键数据如下
Content-Disposition: form-data; name="upload_file"; filename="test1.php"
Content-Type: application/x-php
修改content-type为image/png
释放数据包,检查靶场服务器上传路径,发现上传成功。
3.3 Pass-03 :黑名单疏漏
代码解析
$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); //收尾去空
$deny_ext = array('.asp','.aspx','.php','.jsp');
这句代码中限制文件后缀中出现asp,aspx,php,jsp几种类型就无法上传,但是服务器往往会配置php3和php5也能够进行解析,我们发现了开发者的疏漏,因此只需要抓包修改文件后缀名即可
绕过方法
抓包,关键部分如下
Content-Disposition: form-data; name="upload_file"; filename="test1.php"
Content-Type: application/x-
修改文件后缀为php5,释放数据包
查看服务器路径,上传成功!
3.4 Pass-04:黑名单疏漏 .htaccess文件
概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页重定向、自定义404页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、 禁止目录列表、配置默认文档等功能。通过上传这样的文件,我们能够规定文件的解析方式:如所有的jpg都当作php脚本执行。
假设xigua.jpg文件中带有恶意代码,需要上传到服务器上的htaccess文件内容就可以这样构造
<IfModule >
setHandler application/x-httpd-php #在当前目录下,所有文件都会被解析成php代码执行
</IfModule >
或者指定文件名包含xigua作为php解析执行
<FilesMatch "xigua">
setHandler application/x-httpd-php
</FilesMatch>
代码解析
$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")
黑名单中没有包含htaccess后缀,因此可以正常上传配置文件,之后再上传设计好的后门脚本(如xigua.jpg)就能够执行
绕过方法(略)
【说明:这个方法只适用apache且对php版本有要求,不通用,了解即可】
3.5 Pass-05: 黑名单疏漏 .ini文件
与pass-04思路类似,且有局限性不加以介绍
3.6 Pass-06: 黑名单大小写绕过
代码解析
$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",".ini");
$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); //首尾去空
关键代码中并没有对大小写进行限制,因此采用大小写混合就能够绕过过滤机制
绕过方法
burp抓包修改后缀后放出
查看路径发现上传成功
3.7 Pass-07 :黑名单空格绕过
代码解析
$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",".ini");
$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
观察源码,并没有对文件名进行去空格处理,那么这里我们可以尝试末尾加入空格绕过黑名单
绕过方法
burp抓包,尝试在文件名结尾加空格,释放数据包
上传脚本中,代码功能为打印php信息,拖拽图片新建页面,发现需要的信息出现,说明上传成功!
3.8 Pass-08 : 黑名单后缀’.'绕过
代码解析
$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",".ini");
$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); //首尾去空
这一关对比前几关过滤少了这一句过滤代码: $file_name = deldot($file_name);//删除文件名末尾的点
这样一来,我们可以尝试在末尾加”.“绕过过滤机制,由于某些系统的特性,文件后缀名最后的”.“会被删除,因此上传这样命名的文件依然能够在服务器上执行。
绕过方法
burp抓包,在文件名最后加"."
3.9 Pass-09:黑名单::$DATA绕过
代码解析
$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); //首尾去空
这段过滤代码中不包含$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
因此可以考虑用这种思路绕过:php+windows搭建网站:如果”文件名“+”:: D A T A 会 把 “ : : DATA会把“:: DATA会把“::DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名。
绕过方法
利用windows特性,可在后缀名中加”:: D A T A 绕 过 , 它 的 作 用 就 是 不 检 查 后 缀 名 , 如 : " x i g u a . p h p : : DATA绕过,它的作用就是不检查后缀名,如:"xigua.php:: DATA绕过,它的作用就是不检查后缀名,如:"xigua.php::DATA",Windows会自动去掉末尾的::$DATA成"xigua.php"。
burp抓包,在文件名后缀中加入::$DATA
【注意,这里有坑,上传成功后,直接访问会出现如下403报错】
解决方法也很简单,url中去掉后缀中的::$DATA
3.10 Pass-10:黑名单文件后缀构造绕过
代码解析
$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); //首尾去空
代码首先删除的是文件末尾的点 也就是说 file.php.经过处理后会变成file.php触发过滤机制
接着代码做的是取从右往左的第一个"."后的字符串,假设file_name为file.php在执行后会取出.php
之后就如注释中所写的一样,转换后缀为小写,将后缀中的::$DATA替换成空格,去除后缀中的空格
这段过滤代码看似周密,但是百密一疏,当我们知道这样大概的过滤流程后,这个文件上传功能就任人宰割了
假设我们构造的文件名为 "xigua.php. ."会发生什么?
第一行代码会去掉文件名末尾的".",我们构造的文件名就会变成"xigua.php. "
之后会取出". ",程序会帮我们去掉空格,最后拼接回去,我们上传的文件就会变成"xigua.php."在服务器上是可执行的,因此我们这样构建文件名也可以绕过过滤机制
绕过方法
burp抓包,按照上述思路构建后缀名,完成后释放数据包
3.11 Pass-11:黑名单单次过滤绕过
代码解析
$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","ini");
$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;
过滤代码的策略相对先前的关卡高明许多,是将黑名单中存在的后缀名都删除,但是缺陷是只进行了一次过滤,并没有把函数写成递归或循环,因此只需要关机字穿插就能够绕过:例如后缀为pphphp,过滤函数就会删除其中的一个php,后缀最后变为php
绕过方法
burp抓包,构造后缀,完成后释放数据包
4.常规思路:服务器端白名单绕过
4.1 白名单
白名单对比黑名单就显得可靠很多,与黑名单相反,其规定的是系统认定安全的文件后缀名,凡是在白名单之外的都不允许上传。采用这种方式可以防御众多的未知风险,但值得一提,白名单并不能完全可靠,因为我们不能保证搭建网站的全过程中所依赖的搭建资源都是可靠的,如某些中间件就存在这解析漏洞等。白名单只是在编码层面的安全策略。下面主要介绍白名单的00截断绕过:
4.2 Pass-12: 请求头%00截断
【要求:】php版本小于5.3,且php中的魔术引号为OFF状态
在url中%00表示ASCll码中的0,而0作为特殊字符保留,表示字符结束;当url中出现%00时就认为读取已结束,而忽略后面的字符。例如我们构造payload中的文件名为”xigua.php%001.jpg“,采用这样的方法可以绕过过滤函数,文件上传时xigua.php成功上传1.jpg就会被截断。
代码分析
$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;
这段代码获取的是请求头中的save_path加上随机数作为名称,拼接上上传文件的后缀最终形成文件名
知晓这个点后,思路十分清晰了
绕过方法
抓包修改如下两处,释放数据包
这样一来根据先前的代码分析,形成的文件名为test.php%17627462362681.jpg
(随机数是我乱敲)百分号后的内容将会被截断最终变成可执行的脚本文件
【再说一遍,这个方法对php版本有限制,并不通用】
4.3 Pass-13: 请求正文0x00截断
代码分析
$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;
和上一题类似,不过注意上述代码最后一行,可以发现这次名称的获得是在请求正文中,两题思路相似,但需要注意一点: POST
不会对里面的数据自动解码,需要使用十六进制编加上00
截断。
绕过方法
使用burp
中的hex
模块在php
后面a
对应的位置添加00
进行截断,即在1.phpa
十六进制编码中a
对应的hex
编码改为00
找到a的位置,修改十六进制为00
未完待续…