1.关于HttpModule
什么是HttpModule,这个其实对于有JAVA EE基础的师傅来说比较好理解,如果我们把ASP.NET中的HttpHandler类比为Servlet,那么HttpModule就相当于Filter。ASP.NET处理请求的过程是基于管道模型的,大概是这样:
(图来自https://cloud.tencent.com/developer/article/1130844)
也就是说,在请求到达HttpHandler之前,需要经过HttpModule,很多时候鉴权逻辑都会写在这里。
多提一嘴,传统的ASP.NET WebForm应用的鉴权逻辑常见的有三种思路,第一个就是我们上面提到的HttpModule。第二个思路是实现一个所有Page类的父类,在其Init方法中写好鉴权逻辑,然后所有Page类继承这个父类(常见于Aspx文件的鉴权)。第三个思路和上一种类似,实现一个所有Handler类的父类,其ProcessRequest方法实现鉴权逻辑,然后结合事件机制进行鉴权(常见于ashx文件的鉴权)。
说回正题,那么这个HttpModule机制是否可以用来做内存马呢?在思考这个问题之前先来学习一下HttpModule的基本配置和使用。
首先,我们要写一个实现了IHttpModule的类
注意其中要实现的方法,我们要实现Init方法,在其中给特定的事件(例如BeginRequest就是一个事件)进行订阅。这里如果你暂时搞不懂什么是事件也没关系,你只需要知道在处理请求的不同阶段会触发不同的事件,ASP.NET请求的整个生命周期可能触发很多事件,包括但不限于如下:
例如BeginRequest就是请求开始的时候触发的事件,EndRequest则是结束时触发的事件。那么触发事件之后做什么呢?上面我们以委托的形式传入了Application_BeginRequest、Application_EndRequest两个方法,不难推测触发事件时会分别调用这两个方法。
写好这个类之后来配置一下
<system.webServer>
<modules>
<add name="HelloWorldModule" type="WebApplication1.HelloWorldModule" />
</modules>
</system.webServer>
然后访问网站看看效果
可以看到确实触发了对应的方法。比较有意思的一点是,就算访问一个不存在的页面,在请求的结束阶段依然会调用对应的方法
这样我们就简单的配置和使用了一下HttpModule,我们发现,它和Filter还有一点神似。我们写好Filter实现类也可以去web.xml里配置,这边写好HttpModule是去web.config里配置。ASP.NET那么一定有从web.config里读取相关配置,并进行HttpModule注册加载的过程,如果我们能自己实现这个过程,那么无疑就可以实现内存马。
2.尝试构造内存马
那么是在哪里进行HttpModule的注册呢?来到Httpapplication类,很容易注意到一个Initmodules方法,看起来就和HttpModule初始化有关
跟进InitModules()方法,注意到这里先从配置信息中取出HttpModules的配置信息,接着会创建一个httpModuleCollection,并且赋值给_moduleCollection,有之前分析别的内存马的经验,看到这个Collection,容易联想到HttpModules应该就存放在这里,最后又调用一个InitModulesCommon()方法
跟进InitModulesCommon()方法,注意到这里主要就是遍历_moduleCollection,依次调用所有HttpModule的Init()方法,完成初始化操作
所以第一个想法,我们自实现一个恶意的HttpModule类,将其加入到HttpModuleCollection,然后调用InitModulesCommon(),进行注册操作。
HttpModuleCollection提供一个AddModule()方法,传入Module名字和实现类就可以将其添加到HttpModuleCollection里面。
乍一看我们接下来的思路很清晰了,就是尝试调用这个AddModule()去注册恶意Module
然而通过AddModule()往HttpModuleCollection直接添加Module的思路是行不通的,会出现报错,.NET压根不允许我们后续再执行一次Module的Init(),尝试各种操作都8行,后续学习了一波鲁平师傅在.NET安全矩阵里的分享,了解到了原因,这是因为InvocationFlags,代码如下:
https://referencesource.microsoft.com/#mscorlib/system/reflection/fieldinfo.cs,533cc59fbe48130c
并且这玩意只有get,我们也没办法给他改成别的值。这条路看来是走不通了。
3.正确的构造思路
来回顾一下我们的目的,我们是想把自己的HttpModule放进HttpModuleCollection,并执行InitModulesCommon(),这样可以触发我们HttpModule里的Init()方法完成初始化。那我们执行Init方法的目的是什么?其实就是为了执行类似下面这样的事件订阅:
application.BeginRequest += (new EventHandler(this.EvilCalc));
因此我们不必非得触发Init(),只要能完成上面这个行为,把我们的恶意方法挂到指定事件上就行。那么上面这个行为具体是怎么实现的呢?跟进BeginRequest事件,可以看到这个写法有有些不一样,add和remove要怎么触发?其实很简单。
Add是事件的添加器(add accessor),用于订阅事件。当其他代码使用 += 操作符来订阅 BeginRequest 事件时,会调用这个方法。那么举一反三,remove就是-=的时候触发
所以可以看到,我们上面执行事件订阅操作的时候,实际上是触发了AddSyncEventHookup()方法
我们可以用反射去逐步实现这个方法的功能,用反射依次调用下面几个方法即可
Events.AddHandler()
GetModuleContainer()
SyncEventExecutionStep()
moduleContainer.AddEvent()
上述大部分方法以及他们接受的传参我们都好理解,唯一一个问题就是这个GetModuleContainer()接受的moduleContainer传参是什么:
这里会一直调用GetModuleContainer(),每次的moduleName都不一样。实际上在一开始Web Forms会加载一些默认的module,比如我们上面看到的OutputCache、Session等等,就是自带的一些Module,因此调用GetModuleContainer()的时候传入一些系统默认加载的Module的名字就行(实际上这里也有讲究,不过后面再说)
这里参考鲁平师傅的代码,首先创建一个类(假设为F类),其有一个内部类CustomModule(恶意Module实现类),其中实现一个恶意方法OnEnter,触发内存马则执行whoami /priv并把结果写到Temp1.txt中
然后通过反射调用关键方法自实现的AddSyncEventHookup()逻辑,我们在F类的构造方法里写好如下逻辑,全程使用反射调用AddSyncEventHookup()里面的关键方法。首先是关于Events.AddHandler()的调用,如下:
接着是GetModuleContainer()的调用,如下:
再接着是SyncEventExecutionStep()的调用:
最后是AddEvent()的调用
这样就实现了AddSyncEventHookup()的功能,最后调用F类构造方法即可实现内存马注入,先访问内存马注入器:
然后多访问几次各种功能
成功触发Module
4.优化代码
上述代码确实已经可以打入一些恶意代码了,但是想注入哥斯拉内存马还有一点问题,因为LogRequest发生时间靠后,这时候session已经被回收了,是获取不到的,而哥斯拉又需要依赖session。所以得找一个发生在session回收之前的事件。鲁平师傅这里找的是自带的Session Module+AcquireRequestState事件
然后把原先的OnEnter()方法的位置改成哥斯拉webshell即可。但是不得不吐槽,HttpModule型内存马感觉真的挺不稳定,有时候能触发,有时候不能。(也可能是我的打开方式不对或者环境有问题,欢迎师傅们指点)
5.总结
以HttpModule机制制作的内存马,或者换句话说,其核心思想是把恶意方法绑定到对应的事件,感觉隐蔽性和优先级还是很不错的,如果不要求注入哥斯拉内存马,而只是实现一般的命令执行代码的话,还是有很多事件可供我们选用的。美中不足的一点是触发不稳定,感觉还有很多改进的空间
6.参考
非常感谢鲁平师傅在.NET安全矩阵星球中分享的有关HttpModule内存马的文章,学到了很多