一次白名单绕过分析

0X00 前言

文件上传获取服务器权限,一般分为黑名单,以及白名单上传至于是否解析这就是后话了,其中黑名单,见名知其意不允许上传的文件后缀,主要表现于服务器不允许上传在黑名单数组内的后缀文件,主要探测手段为如下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;
        }
    }
}

}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值