0X0 前言
文件上传获取服务器权限,一般分为黑名单,以及白名单上传至于是否解析这就是后话了,其中黑名单,见名知其意不允许上传的文件后缀,主要表现于服务器不允许上传在黑名单数组内的后缀文件,主要探测手段为如下demo示例,如果任意不存在的后缀能够上传成功,即表明服务器对文件的校验为黑名单
Content
-
Disposition
:
form-data
;
name=
"file"
;
filename=
"1.jpgsssss"
而黑名单的绕过主要是针对于黑名单中的遗漏后缀来进行,如Net中可执行的后缀 aspx、ashx、asmx、cshtml、asp、soap等,如果有粗心的程序员遗漏了则可能会导致服务器失陷,其次针对于黑名单同样也可以利用win系统特性类似于%00截断(个人理解),但是%00截断是PHP的特性主要作用于PHP 5.2.x版本
会自动重命名为
而且特殊字符也可以在程序中引起报错,可能获取上传路径
另外如果web.config 文件没有被限制上传的话也可以利用web.config,具体参考:https://xz.aliyun.com/t/6037
另一种就是老生常谈的结合中间件来进行攻击了,比如IIS6.0 以及特定环境下的IIS7.5解析漏洞 Nginx用户配置不当导致的解析漏洞,以及apache的畸形解析
白名单的话只能上传允许的后缀文件,相对于黑名单安全的很多,但是也并不是绝对安全的,结合中间件,一些特定的环境也是可以绕过的,接下来让我们看看我在实战中遇见绕过白名单案例
0X01 绕过白名单
在一次公司项目测试中遇见了该系统(经典开局登陆框),和老大一通乱搞,发现不对头搞不进去立即转换思路通过fofa批量收集使用该系统的站点,并通过批量跑备份文件,成功get bin.zip开启灰盒测试之路
发现一处上传方法
其中ProcessRequest方法如下,接收了一个名为hash的参数赋值给val之后通过val.IsNullOrEmpty()判断hash是否为空,如果为空则判断是否有上传数据包,如果有则进入 CommonSave跟进查看
// Token: 0x020000C2 RID: 194
public
class
MyUpload
:
IHttpHandler
{
// Token: 0x060002B2 RID: 690 RVA: 0x000222A0 File Offset: 0x000204A0
public
void ProcessRequest(HttpContext context)
{
HttpRequest request = context.Request;
string
val
= request[
"hash"
];
int count = request.Files.Count;
string text = string.Empty;
if
(
val
.IsNullOrEmpty())
{
if
(count >
0
)
{
text =
this
.CommonSave(context);
}
}
else
{
text =
this
.SliceSave(context);
}
if
(text.Equals(
"errorFile"
))
{
context.Response.StatusCode =
403
;
context.Response.Write(
"{fileName:"不支持文件类型!"}"
);
context.Response.End();
return
;
}
this
.Finish(
this
.GetResponseJSON(request,
""
, text));
}
跟进CommonSave发现最后进行了文件保存的操作,我们直接看最后的filename是否可控,从下往上回溯
发现text4由string.Format(*) 拼接,往上追踪一下text
text
由拼接获取
`text = StringUtils.GetGUID() + "." + extenName;`
而
extenName
又是通过
`string extenName = Path.GetExtension(text);`
获取
而最开始的
text
是通过
`string text = context.Request["fileName"];`
获取
获取到extenName后还会进入if语句,判断了text不为空&& extenName在允许上传的名单中
if
(!
text
.
IsNullOrEmpty
()
&&
!
PageClass
.
GetDocumentType
().
Any
((
string
x
)
=
>
x
.
Equals
(
extenName
,
StringComparison
.
CurrentCultureIgnoreCase
)))
{
return
"errorFile"
;
}
无解,回到最初的地方hash不为空跟进SliceSave
方法
先获取了一个fileName如果不为空则进入if语句中判断后缀是否在白名单中
之后又获取了一个hash赋值给text如果不为空则进入if语句中判断后缀是否在白名单中
继续走逻辑,将text拼接”.yqdata” 赋值给text2,之后获取一个fileenter参数赋值给text3,后面通过
string
text4 =
string
.Format(“/{
0
}/{
1
}”, text3, DateTime.Now.ToString(“yyyyMMdd”));
建立上传文件夹并判断文件夹是否存在不存在则创建
继续跟进,将text4以及text2拼接复制给text5,当前的text5的值为:/text3/年月日/text.yqdata 在通过string text6 = context.Server.MapPath(text5);
将路径给text6
之后获取一个action参数判断是否为query不是进入else分之,如果这里是query将不会进入任何文件写入操作,所以我们随意给action复制即可,继续跟进else
进行了数据流写入操作,之后判断ok参数是否为1,如果不为1则会进入Finsh
Finsh不为1会直接终止方法,所以我们这里ok必须为1
之后判断了text6路径文件是否存在,存在进入,这里就是将text6的文件复制给text7,而text7是从
string
text7 = text6.Substring(
0
, text6.LastIndexOf(“.”)) + extension;
extension 又是从string extension = Path.GetExtension(text2);
获取到的后缀
一路走下来感觉没啥问题,但是往往事情没有那么简单,我们思考一下如果context.Request["fileName"]
fileName值为空呢,那么text7 的路径将会变为
string
text7 = text6.Substring(
0
, text6.LastIndexOf(“.”))
我们在回去看看text6的是咋组成的
Text6
= text5 ,text5 = text4/text2,text2 = text+”.yqdata”
再看看text
于是乎我们可以通过构造这样的文件来绕过白名单限制,主要控制text hash=11111.asp. 还记得我们最开始说的 . 会被自动重命名的问题,这就是一个实战案例由于我们的hash = 1111.asp. 会经过下面的if语句会通过Path.GetExtension(text); 来获取后缀,但是我们的最后是一个点没有后缀进入if语句时条件为假,绕过了限制,至此白名单Get
验证:
0X02 总结
好耶,成功收获老大赞赏,至于什么情况下用什么方法还得结合系统,以及程序员大哥留口饭吃,代码不要写太好,如果有什么问题欢迎指证
具体程序代码如下,有兴趣的可以看看,感觉还是有意思的
using
System;
using
System.IO;
using
System.Linq;
using
System.Web;
using
YQExam.Common;
namespace
YQExam.Web.Manage.Ajax
{
// Token: 0x020000C2 RID: 194
public
class
MyUpload
:
IHttpHandler
{
// Token: 0x060002B2 RID: 690 RVA: 0x000222A0 File Offset: 0x000204A0
public
void
ProcessRequest
(
HttpContext context
)
{
HttpRequest request = context.Request;
string
val = request[
"hash"
];
int
count = request.Files.Count;
string
text =
string
.Empty;
if
(val.IsNullOrEmpty())
{
if
(count >
0
)
{
text =
this
.CommonSave(context);
}
}
else
{
text =
this
.SliceSave(context);
}
if
(text.Equals(
"errorFile"
))
{
context.Response.StatusCode =
403
;
context.Response.Write(
"{fileName:"不支持文件类型!"}"
);
context.Response.End();
return
;
}
this
.Finish(
this
.GetResponseJSON(request,
""
, text));
}
// Token: 0x060002B3 RID: 691 RVA: 0x0002233C File Offset: 0x0002053C
private
string
CommonSave
(
HttpContext context
)
{
HttpPostedFile httpPostedFile = context.Request.Files[
0
];
string
text = context.Request[
"fileName"
];
if
(text.IsNullOrEmpty())
{
text = Path.GetFileName(httpPostedFile.FileName);
}
string
extenName = Path.GetExtension(text);
if
(!text.IsNullOrEmpty() && !PageClass.GetDocumentType().Any((
string
x) => x.Equals(extenName, StringComparison.CurrentCultureIgnoreCase)))
{
return
"errorFile"
;
}
text = StringUtils.GetGUID() +
"."
+ extenName;
string
text2 = context.Request[
"fileenter"
];
if
(text2.IsNullOrEmpty())
{
text2 =
"documentFiles"
;
}
string
text3 =
string
.Format(
"/{0}/{1}"
, text2, DateTime.Now.ToString(
"yyyyMMdd"
));
if
(!Directory.Exists(context.Server.MapPath(text3)))
{
Directory.CreateDirectory(context.Server.MapPath(text3));
}
string
text4 =
string
.Format(
"{0}/{1}"
, text3, text);
string
filename = context.Server.MapPath(text4);
httpPostedFile.SaveAs(filename);
return
text4;
}
// Token: 0x060002B4 RID: 692 RVA: 0x0002245C File Offset: 0x0002065C
private
string
SliceSave
(
HttpContext context
)
{
string
fileName = Path.GetFileName(context.Request[
"fileName"
]);
if
(!fileName.IsNullOrEmpty())
{
string
sextenName = Path.GetExtension(fileName);
if
(!PageClass.GetDocumentType().Any((
string
x) => x.Equals(sextenName, StringComparison.CurrentCultureIgnoreCase)))
{
return
"errorFile"
;
}
}
string
text = context.Request[
"hash"
];
if
(!text.IsNullOrEmpty())
{
string
sextenName = Path.GetExtension(text);
if
(!sextenName.IsNullOrEmpty() && !PageClass.GetDocumentType().Any((
string
x) => x.Equals(sextenName, StringComparison.CurrentCultureIgnoreCase)))
{
return
"errorFile"
;
}
}
string
text2 = text +
".yqdata"
;
string
text3 = context.Request[
"fileenter"
];
if
(text3.IsNullOrEmpty())
{
text3 =
"documentFiles"
;
}
string
text4 =
string
.Format(
"/{0}/{1}"
, text3, DateTime.Now.ToString(
"yyyyMMdd"
));
if
(!Directory.Exists(context.Server.MapPath(text4)))
{
Directory.CreateDirectory(context.Server.MapPath(text4));
}
string
text5 =
string
.Format(
"{0}/{1}"
, text4, text2);
string
a = context.Request[
"action"
];
int
count = context.Request.Files.Count;
string
text6 = context.Server.MapPath(text5);
if
(a ==
"query"
)
{
string
str = context.Request[
"extname"
];
string
text7 = text5.Substring(
0
, text5.LastIndexOf(
"."
)) + str;
if
(File.Exists(text7))
{
this
.Finish(
this
.GetResponseJSON(context.Request,
","ret":1"
, text7));
}
else
if
(File.Exists(text6))
{
this
.Finish(
new
FileInfo(text6).Length.ToString());
}
else
{
this
.Finish(
"0"
);
}
}
else
{
if
(count >
0
)
{
HttpPostedFile httpPostedFile = context.Request.Files[
0
];
using
(FileStream fileStream = File.Open(text6, FileMode.Append))
{
byte
[] array =
new
byte
[httpPostedFile.ContentLength];
httpPostedFile.InputStream.Read(array,
0
, httpPostedFile.ContentLength);
fileStream.Write(array,
0
, array.Length);
}
}
if
(!(context.Request[
"ok"
] ==
"1"
))
{
this
.Finish(
"1"
);
}
if
(File.Exists(text6))
{
text2 = context.Request[
"fileName"
];
string
extension = Path.GetExtension(text2);
string
text7 = text6.Substring(
0
, text6.LastIndexOf(
"."
)) + extension;
int
num =
0
;
while
(File.Exists(text7))
{
string
str2 = text7.Substring(
0
, text7.LastIndexOf(
"."
));
string
str3 = DateTime.Now.ToTimeStamp().ToString();
int
num2 = num;
num = num2 +
1
;
text7 = str2 + str3 + num2.ToString() + extension;
}
File.Move(text6, text7);
}
}
return
text5;
}
// Token: 0x060002B5 RID: 693 RVA: 0x000227A0 File Offset: 0x000209A0
private
string
GetResponseJSON
(
HttpRequest request,
string
result,
string
fileName
)
{
string
text = request[
"type"
];
string
text2 = request[
"user"
];
string
text3 =
""time":""
+ DateTime.Now.ToString(
"yyyy/MM/dd HH:mm:ss"
) +
"""
;
if
(text !=
null
)
{
text3 = text3 +
","type":""
+ text +
"""
;
}
if
(text2 !=
null
)
{
text3 = text3 +
","user":""
+ text2 +
"""
;
}
if
(fileName !=
null
)
{
text3 = text3 +
","name":""
+ fileName +
"""
;
}
return
"{"
+ text3 + result +
"}"
;
}
// Token: 0x060002B6 RID: 694 RVA: 0x00002C42 File Offset: 0x00000E42
private
void
Finish
(
string
json
)
{
HttpResponse response = HttpContext.Current.Response;
response.Write(json);
response.End();
}
// Token: 0x1700000E RID: 14
// (get) Token: 0x060002B7 RID: 695 RVA: 0x00002C3F File Offset: 0x00000E3F
public
bool
IsReusable
{
get
{
return
false
;
}
}
}
}
原文地址:
https:
/
/xz.aliyun.com/t
/12617
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权!
原文始发于微信公众号(白帽子左一):一次白名单绕过分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论