1.LosFormatter使用以及分析
还是先来看看LosFormatter的简单使用吧,这里也用Y4er师傅的demo来学习一下先
先写一个可以序列化与反序列化的类,其中有name和age两个属性,一个SayHello方法
然后调用LosFormatter的Serialize()方法进行序列化,通过其Deserialize()方法对序列化数据进行反序列化,然后对反序列化出来的对象调用SayHello()方法,同时输出序列化内容,结果如下:
可以看到SayHello方法被成功调用,并且也拿到了序列化数据,不难发现这个序列化数据其实就是base64格式的,并且以/wEyn开头,实战中如果看到传参里有数据以/wEyn开头,就可以推测其后端可能对传参进行Losformatter反序列化。
当然,这里我们不仅要学习Losformatter的用法,还很有必要分析一下Losformatter序列化与反序列化时的具体流程,因为它还和ObjectStateFormatter有联系。
首先先来看LosFormatter的构造函数,这里其实有三个,如果我们单纯LosFormatter()的时候就是正常形态,将this._formatter设置为new ObjectStateFormatter()
还有另外两个形态,当你LosFormatter()的第一个参数设为true,第二个参数设为string或者byte[]类型的macKeyModifier时,将this._formatter设置为
new ObjectStateFormatter(macKeyModifier)
那么再来看LosFormatter的序列化流程:
实际上走的流程是
Serialize(Stream stream, object value) -> Serialize(TextWriter output, object value) -> SerializeInternal(TextWriter output, object value)
注意到这里调用this._formatter属性的Serialize()方法,也就是说
LosFormatter().Serialize()
实际上就等于
ObjectStateFormatter().Serialize()
反序列化流程同样如此
LosFormatter().Deserialize()
也等于
ObjectStateFormatter().Serialize()
当然,这里我想更近一步,我们再去解读ObjectStateFormatter()的序列化与反序列化流程吧,首先是这个Serialize()方法
继续跟进,记住这个方法
private string Serialize(object stateGraph, Purpose purpose)
这个方法略复杂,我们来讲讲重点
首先看到这个方法里又调用了一个Serialize()方法的重载,传入memoryStream
这个重载方法里有一个重点
就是这个SerializeValue()方法,用于在序列化数据时设置一些初始信息,跟进SerializeValue方法,这个方法也很复杂,它的功能大概是在序列化数据开头设置一些信息,并且进行序列化
这个信息会直接影响后续反序列化的行为,我们可以下断调试一下看看这里会设置什么信息,这里用dnspy下断大概率无法断住,用VS反编译后下断容易出现“当前不会命中断点,还没有为该文档加载任何符号”的错误,参考这篇文章的方法(改配置+函数断点结合使用)即可解决
https://blog.csdn.net/gdxb666/article/details/127929418
这里会往序列化数据里写入一个50(当然前面还写入了一些值,后续会提到)
同时可以看到,默认情况下SerializeValue()的底层还是使用BinaryFormatter()进行的序列化。因此,在默认情况下,LosFormatter()序列化与ObjectStateFormatter()序列化等价,而ObjectStateFormatter()序列化的底层又靠BinaryFormatter()实现。
但还记得我们前面提到的,LosFormatter()构造函数有多个重载,有一个macKeyModifier参数吗,这个是干什么的呢?
我们注意ObjectStateFormatter()构造方法:
当有macEncodingKey传入时,给_macKeyBytes属性赋值,同时给_forceLegacyCryptography属性赋值为true,那么这有什么用呢?现在回到我们的序列化流程,继续往后看
private string Serialize(object stateGraph, Purpose purpose)
我们前面提到,这个Serialize()重载的调用就实现了序列化流程,它后面还有一大段代码是在干什么?
在默认情况下,使用BinaryFormatter序列化数据后,直接对array(也即SerializeValue()生成的序列化数据)进行base64编码并返回
而当我们使用另一个LosFormatter()构造方法
会导致序列化流程发生一些变化
看看这个流程的前后文,我们不难推测这个前后文是想对序列化数据先进行加密操作(EncryptOrDecryptData负责加解密,GetEncodedData实际上是负责验签)。后续对ViewState反序列化问题进行学习和分析的时候再来好好看看这个点。
序列化的时候使用
new LosFormatter(true,"testtesttest")
去生成序列化数据,反序列化的时候使用
new LosFormatter()
去对上面的数据进行反序列化,依然能够成功:
这个反序列化数据确实和之前不一样了
但是如果反过来,序列化的时候使用
new LosFormatter()
反序列化使用
new LosFormatter(true, "testtesttest")
就不行了
在这个错误里,我们可以捕捉到一个关键词——ViewState,是的,这个地方的逻辑涉及到.NET的一个经典反序列化问题,ViewState反序列化,后续我们还要回来深入分析这个流程。
总之现在我们先总结一下LosFormatter序列化的流程:
1.调用LosFormatter()进行序列化,则其调用ObjectStateFormatter()序列化,ObjectStateFormatter()序列化调用BinaryFormatter()进行序列化,且过程中往序列化数据的开头部分写入”255”,”1”,”50”
2.(如果设置了密钥且和viewstate有关,存在一个加密、签名相关流程)
3.将序列化数据Base64编码并返回
那我们猜想LosFormatter反序列化就是一个反着来的过程,事实上也确实如此,反序列化过程中要先进行Base64解码,并且可以看出后续也有验签和解密相关流程
继续跟进这个Deserialize()
注意看,这里先读取序列化数据第一位Byte,判断是不是255,再读取一位,判断是不是1,如果都符合,那么进入到DeserializeValue()方法,我们猜测这个方法的逻辑和前面提到的SerializeValue()也刚好是反过来的,由于我们开头是255和1,因此能进入DeserializeValue()
这个方法里再读一位byte,并进入switch,在读一位就是50,因此进入到case 50的分支
这个分支使用BinaryFormatter()对余下的数据进行反序列化,因此LosFormatter()反序列化流程本质依然是BinaryFormatter(),流程如下:
1.将序列化数据Base64解码
2. (如果设置了密钥且和viewstate有关,存在一个解密、验签相关流程)
3. 调用LosFormatter()进行反序列化,则其调用ObjectStateFormatter()反序列化,ObjectStateFormatter()反序列化过程中,判断序列化数据的开头部分是否是”255”,”1”,”50”,如果是,则调用BinaryFormatter()进行反序列化。
综上,我们就大概分析完了LosFormatter()序列化与反序列化的流程,还有一个ObjectStateFormatter加密情况下的序列化与反序列化还没有分析,LosFormatter()本身的流程并不复杂,真正复杂的流程隐藏在ObjectStateFormatter()当中,后续在ViewState反序列化场景下,我们还要再更详细地看看ObjectStateFormatter()。
2.ClaimsIdentity链
https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/mscorlib/system/security/claims/ClaimsIdentity.cs#L18
其反序列化构造函数调用本类的Deserialize()方法进行反序列化
这个方法一开始就设置了一个BinaryFormatter(),其中显而易见的,当获取到的enumerator.Name是ActorKey或者BootstrapContextKey的时候,就会触发BinaryFormatter反序列化,反序列化的内容从info的BootstrapContextKey或ActorKey取得
此外,case为ClaimsKey的时候,进入DeserializeClaims方法,也会进行BinaryFormatter反序列化
那么这些什么ActorKey或者BootstrapContextKey是什么乱七八糟的东西呢?
可以看到,是
System.Security.ClaimsIdentity.actor
或
System.Security.ClaimsIdentity.bootstrapContext
或
System.Security.ClaimsIdentity.claims
也就是说,当你的info里存在下面这个变量的时候,就会获取它的值,进行一次base64解码,并进行反序列化
System.Security.ClaimsIdentity.bootstrapContext
3.WindowsIdentity链
这个并没有什么好说的,来到其反序列化构造函数
base(info)会调用其父类的构造方法,而WindowsIdentity的父类恰好是我们前面分析的ClaimsIdentity
这里写一个ClaimsIdentity链的生成器,序列化时给下面的变量赋值
System.Security.ClaimsIdentity.bootstrapContext
这里用LosFormatter对我们构造好的WindowsIdentity进行序列化与反序列化
成功RCE。
4.WindowsClaimsIdentity链
这个链子很神秘,之所以说他神秘是因为这玩意位于
Microsoft.IdentityModel.Claims
命名空间,在microsoft.identitymodel.dll,找了我老半天,这个链子是有限制的
来到其反序列化构造方法
注意到调用了本类的Deserialize方法,其本类的Deserialize方法又调用了一连串例如DeserializeActor()、DeserializeLabel()的方法
前面几个虽然说是Deserialize方法,但实际上并没有反序列化操作,就是单纯的从info里取值
但最后一个DeserializeActor方法不同,发现其从info里获取_actor的值,然后进行一次base64解码后进行binaryFormatter反序列化,没错,这又是一个二次反序列化点(
除此之外还要往info里塞一些变量,都没啥好说的,都不用分析,借助异常信息其实就知道缺哪些了:
和我们接口测试时的找参数思想有异曲同工之妙
最后成功RCE
5.SessionSecurityToken链
学习这个链之前先让GPT告诉我们如何用XmlDictionaryReader处理XML数据
此外还有两个常见的方法IsStartElement 和 ReadStartElement
现在来分析这个链
来到SessionSecurity()类反序列化构造方法,可以看到它从info里取出SessionToken的值,然后传入
XmlDictionaryReader.CreateTextReader()
进行XML的处理,拿到一个XmlDictionaryReader类的对象。所以我们最后的整个XML payload需要在序列化的时候添加在info的SessionToken变量里
中途就是从Xml里获取和处理各种信息,我们先不关注,接着会运行到找一部,指针先定位到ClaimsPrincipal,然后把
XmlDictionaryReader
对象传入ReadPrincipal()方法
跟进ReadPrincipal方法,这里会进入ReadIdentities()方法
跟进ReadIdentities()方法,也是对XML里面的信息做了各种处理后,进入ReadIdentity()方法
注意到这个方法的最后,这里是对BootstrapToken元素进行处理,读取这个元素的值,然后后续又进行一个binaryFormatter反序列化。
是的,这又双叒叕是一个二次反序列化点,不过是一个花式二次反序列化点。只不过在封装payload的时候,要把XML里的各种属性和属性值赋值好,最后得到的xml对象转成Array形式赋值给info里的SessionToken即可,来看一下ysoserial.net是怎么生成这个链子的payload的
如图,就是要配置好各种键值,然后赋值,尤其是最关键BootStrapToken,最后就是对它的值进行base64解码并反序列化导致的RCE。
做好一个恶意xml对象后赋值给info的SessionToken
这里还有一个值得注意的细节是反序列化构造方法里会判断是否存在SecurityContextToken属性
如果不存在后续会触发异常,所以在构造xml内容的时候记得把这个加上。
6.总结
分析了一下LosFormatter、ObjectStateFormatter和BinaryFormatter的关系,并且分析了几条链子(你们.NET的二次反序列化链是真多),当然关于ObjectStateFormatter还有很多东西值得说,留到后面ViewState反序列化问题时再来详细分析一下吧。
7.参考
https://xz.aliyun.com/t/9597
https://blog.csdn.net/gdxb666/article/details/127929418