TypeConfusedDelegate以及魔改yso
又一个Exchange实战
前因
这次的起因是一个日常的项目,探测版本发现是 Exchange2019CU11
小版本号 15.2.986.5
通过版本判断存在草老师的 CVE-2021-42321 漏洞
这个漏洞是通过ews接口触发的针对 UserConfiguration
的反序列化漏洞,网上也已经有公开的exp了
条件是拥有一个用户的凭据,通过ysoserial_net,使用TypeConfusedDelegate
gadget生成payload发送即可。
不过针对这个目标在利用时遇到一些阻碍,正好之前没有怎么认真学习过.net的反序列化问题,所以就单独学了一下来解决问题。
这篇文章以下就不详述 CVE-2021-42321 了,具体的细节以及exchange的反序列化问题我比较想单独放一篇文章。
越看越觉得不懂的越来越多了。。。
过程
利用的话需要用户凭据,最直接的方法是通过工具或平台收集目标域名的邮箱账号,然后利用exchange的ews
或者autodiscover
接口进行爆破
而这次也是很幸运地爆破出了几个账号的口令。
于是直接利用exp
1 |
|
结果。。。白茫茫一片啥也没有
exchange因为安装以及更新的需求基本是需要出网的,所以这里大概率是exp没有成功。
于是将脚本的每一次请求的结果都打印了出来,发现了问题,最后一步,也就是触发反序列化的请求返回了500。
那这里有几种可能:
- 漏洞通过某些方式的改动被修补了
- gadget类型被黑了,因为不允许的类型会直接抛出错误
- 起进程的动作被杀软拦截了
- payload传入直接被拦
我尝试将payload修改了几个字符,就成功返回200了,结合一些其他的结果,大致判断是因为exchange上存在ATP之类的防护软件,将Process给拦截了。
魔改Yso
yso反序列化的利用都是起cmd进行命令执行,而当前的许多防护、EDR、ATP等都把 Process.Start 拦截的死死的,所以只能尝试修改一下yso_net小工具来尝试RCE。
新版的Yso已经有草老师提的直接通过**Assembly.Load()**加载dll来RCE的功能
不过将他直接放进 TypeConfusedDelegate
有点麻烦,所以我选择更方便的做法,通过反序列化来直接写一个webshell。
TypeConfusedDelegate
本次反序列化用到TypeConfusedDelegate
链,所以我们先来简单看一下TypeConfusedDelegate链的大概原理
TypeConfusedDelegate 的释义为:类型混淆委托,那么两个要点就是*委托*** 、*类型混淆***
委托和多播委托
委托可以理解为一个引用方法的变量,很像是C里的指针。委托主要就是c#为了让c++开发者适应没有指针的开发环境而搞出来的一套东西。
1 |
|
这里通过实例化 MyDelegate
来进行对PrintString
的引用以及传参。
需要注意的是传递给委托的方法签名必须和定义的委托的返回值、参数一致。
多播委托则是持有对委托列表的引用,把多播委托想象成一个列表,将委托的方法加入列表中,多播委托会按顺序依次调用每个委托。
1 |
|
我们通过 MulticastDelegate.Combine
合并两个委托。通过多播委托的 GetInvocationList()
可以得到委托的列表。
TypeConfusedDelegate分析
然后再来看看 TypeConfusedDelegate
链,他的核心代码如下:
1 |
|
可以看到它使用了SortedSet<T>
, 顾名思义SortSet<T>
是一个可排序的泛型集合。(泛型意为具体类型可以在声明实例时指定)它是c#里一个重要的数据结构,支持对存储的元素进行排序。
既然可以排序,那一定会涉及到比较,所以SortSet支持传入一个实例化的 ICompare
接口
ICompare接口的Compare方法可以比较两个对象并返回一个整形结果
我们看代码中通过 Compare<T>
类实现了ICompare接口,而参数就是一个 Compasison
类 ,一个 委托 类型的比较器
再来看SortedSet反序列化的过程。在OnDeserialization方法中,首先在序列化流中还原Compare,然后再还原SortedSet的每个元素,并调用Add添加到实例化后的SortedSet中。
我们再回去看YSO的代码,其实他的思路很明确,先创建正常委托,然后通过反射修改正常委托的方法为恶意方法,当SortSet进行序列化的时候,就会触发恶意委托。而c#中的 Func<T>
即是一个万用的泛型委托,可以创建一个任意返回类型的委托。
1 |
|
意为定义一个返回为result类型的M方法的委托,M方法的参数有两个,一个为x类型,一个为y类型
例如把委托的Method设置为Process.Start,元素设置为 cmd
,/c calc
时,就会执行 Process.Start("cmd","/c calc")
弹出一个计算器啦
但是这时候问题出现了:前面说过,**传递给委托的方法签名必须和定义的委托的返回值、参数一致**,Comparison返回的是int,而Process.Start返回的是Process,这就造成了冲突,导致失败,所以代码里使用了多播委托。我们可以直接修改多播委托的_invocatrionList
中的任意一个委托
根据作者的解释,多播委托返回的是一个整型数,即指向进程对象的指针。(这里我也半懂不懂)
OK,这样就完整的进行了TypeConfusedDelegate反序列化!我们再回顾一下
1 |
|
修改Ysoserial
那么根据需求,我们需要的是在最后反序列化的时候,触发的委托不是新建一个进程,而是写入一个webshell
现在我们要做的就是简单的改一下最后使用反射修改的委托的定义
yso本来是更改为一个针对 Process.Start 方法的委托,而我们需要的是一个操作文件的方法,使用 System.IO.File.WriteAllText
即可
WriteAllText方法写文件接受两个参数:WriteAllText(Path, Content)
问题1
但是有一个问题是,Func<T>
是一个需要返回值的泛型委托,像 Process.Start 就会返回生成的Process类型对象
而 File.WriteAllText
返回是 void ,使用 Func 定义的话就会报错,因为Func不支持void返回类型
这个时候就可以使用 Action <T>
委托,他和 Func 基本相同没区别就是**只需要输入参数不需要返回值。**
这样就修改好啦,通过
1 |
|
就可以在 c:\inetpub\wwwroot\aspnet_client
下生成一个内容为123 的1.aspx文件。
问题2
但是当我想通过该方法想写入一些字母的字符串时,却发生了意外,一些字符串无法写入,当时直接进行了一个简单的测试,发现当首字母小于 c
时,就可以成功写入,大于c则不能。当时觉得时一个很奇怪的问题。
当时为了快速解决问题,利用了一个讨巧的方法,在shell的开头加了一个 a
就成功的写入了文件
现在重新理一遍,应该时因为修改的是invokelist[1] 的委托,而输入的参数已经经过了第一个正常的 Compare委托,而第一个参数是 c:\xxxxxxx
故第二个参数若开头大于c,会产生顺序的颠倒,导致错误。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!