1、XmlSerializer反序列化漏洞
Last updated
Last updated
在.NET 框架中的 XmlSerializer 类是一种很棒的工具,它是将高度结构化的 XML 数据映射为 .NET 对象。XmlSerializer类在程序中通过单个 API 调用来执行 XML 文档和对象之间的转换。转换的映射规则在 .NET 类中通过元数据属性来表示,如果程序开发人员使用Type类的静态方法获取外界数据,并调用Deserialize反序列化xml数据就会触发反序列化漏洞攻击(例如DotNetNuke 任意代码执行漏洞 CVE-2017-9822),本文笔者从原理和代码审计的视角做了相关脑图介绍和复现。
.NET 框架中 System.Xml.Serialization 命名空间下的XmlSerializer类可以将 XML 文档绑定到 .NET 类的实例,有一点需要注意它只能把对象的公共属性和公共字段转换为XML元素或属性,并且由两个方法组成:Serialize() 用于从对象实例生成 XML;Deserialize() 用于将 XML 文档分析成对象图,被序列化的数据可以是数据、字段、数组、以及XmlElement和XmlAttribute对象格式的内嵌XML。具体看下面demo
XmlElement指定属性要序列化为元素,XmlAttribute指定属性要序列化为特性,XmlRoot特性指定类要序列化为根元素;通过特性类型的属性、影响要生成的名称、名称空间和类型。再创建一个TestClass类的实例填充其属性序列化为文件,XmlSerializer.Serialize方法重载可以接受Stream、TextWrite、XmlWrite类,最终生成的XML文件列出了TestClass元素、Classname特性和其它存储为元素的属性:
反序列过程:将xml文件转换为对象是通过创建一个新对象的方式调用XmlSerializer.Deserialize方法实现的,在序列化最关键的一环当属new XmlSerializer构造方法里所传的参数,这个参数来自System.Type类,通过这个类可以访问关于任意数据类型的信息,指向任何给定类型的Type引用有以下三种方式。
实例化XmlSerializer传入的typeof(TestClass) 表示获取TestClass类的Type,typeof是C#中的运算符,所传的参数只能是类型的名称,而不能是实例化的对象,如下Demo
通过typeof获取到Type之后就能得到该类中所有的Methods、Members等信息。下图运行Debug时,弹出消息对话框显示当前成员Name的值。
在.NET里所有的类最终都派生自System.Object,在Object类中定义了许多公有和受保护的成员方法,这些方法可用于自己定义的所有其他类中,GetType方法就是其中的一个,该方法返回从System.Type派生的类的一个实例,因为可以提供对象成员所属类的信息,包括基本类型、方法、属性等,上述案例中实例化TestClass,再获取当前实例的Type,如下Demo
第三种方法是Type类的静态方法GetType,这个方法允许外界传入字符串,这是重大利好,只需要传入全限定名就可以调用该类中的方法、属性等
Type.GetType传入的参数也是反序列化产生的漏洞污染点,接下来就是要去寻找可以被用来攻击使用的类。
首先放上攻击链打造成功后的完整Demo,这段Demo可以复用在任意地方(这里不涉及.NET Core、MVC),如下图
只要XmlSerializer存在反序列化漏洞就可用下面Demo中的内容,涉及到三个主要的技术点,以下分别来介绍原理。
ObjectDataProvider类,它位于System.Windows.Data命名空间下,可以调用任意被引用类中的方法,提供成员ObjectInstance用类似实例化类、成员MethodName调用指定类型的方法的名称、成员MethodParameters表示传递给方法的参数,参考下图
再给TestClass类定义一个ClassMethod方法,代码实现调用System.Diagnostics.Process.Start启动新的进程弹出计算器。如果用XmlSerializer直接序列化会抛出异常,因为在序列化过程中ObjectInstance这个成员类型未知,不过可以使用ExpandedWrapper扩展类在系统内部预先加载相关实体的查询来避免异常错误,改写Demo
生成data.xml内容如下:
攻击链第一步就算完成,但美中不足的是因笔者在测试环境下新建的TestClass类存在漏洞,但在生产情况下是非常复杂的,需要寻求Web程序中存在脆弱的攻击点,为了使攻击成本降低肯定得调用系统类去达到命令执行,所以需要引入下面的知识。
ResourceDictionary,也称为资源字典通常出现在WPF或UWP应用程序中用来在多个程序集间共享静态资源。既然是WPF程序,必然设计到前端UI设计语言XAML。 XAML全称Extensible Application Markup Language (可扩展应用程序标记语言) 基于XML的,且XAML是以一个树形结构作为整体,如果对XML了解的话,就能很快的掌握,例如看下面Demo
l 第一个标签ResourceDictionary,xmlns:Runtime表示读取System.Diagnostics命令空间的名称起个别名为Runtime
l 第二个标签ObjectDataProvider指定了三个属性,x:key便于条件检索,意义不大但必须得定义;ObjectType 用来获取或设置要创建其实例的对象的类型,并使用了XAML扩展;x:Type相当于C#中typeof运算符功能,这里传递的值是System.Diagnostics.Process; MethodName用来获取或设置要调用的方法的名称,传递的值为System.Diagnostics.Process.Start方法用来启动一个进程。
l 第三个标签ObjectDataProvider.MethodParameters内嵌了两个方法参数标签,通过System:String分别指定了启动文件和启动时所带参数供Start方法使用。
介绍完攻击链中ResourceDictionary后,攻击的Payload主体已经完成,接下来通过XamlReader这个系统类所提供的XML解析器来实现攻击。
XamlReader位于System.Windows.Markup空间下,顾名思义就是用来读取XAML文件,它是默认的XAML读取器,通过Load读取Stream流中的XAML数据,并返回作为根对象,而另外一个Parse方法读取指定字符串中的XAML输入,也同样返回作为根对象,自然Parse 方法是我们关心和寻求的。
只需使用ObjectDataProvider的ObjectInstance方法实例化XamlReader,再指定MethodName为Parse,并且给MethodParameters传递序列化之后的资源字典数据,这样就可以完成XmlSerializer反序列化攻击链的打造。
从代码审计的角度其实很容易找到漏洞的污染点,通过前面几个小节的知识能发现 序列化需要满足一个关键条件Type.GetType,程序必须通过Type类的静态方法GetType,例如以下demo
首先创建XmlDocument对象载入xml,变量typeName通过Xpath获取到Item节点的type属性的值,并传给了Type.GetType,紧接着读取Item节点内的所有Xml数据,最终交给Deserialize方法反序列化,这是一个近乎完美的利用点。再来看笔者在github上收集到的XmlSerializer反序列化类:XmlSerializeUtil.cs
此处值参数类型为Type,代码本身没有问题,问题在于程序开发者可能会先定义一个字符串变量来接受传递的type值,通过Type.GetType(string)返回 Type对象再传递进DeserializeXml,在代码审计的过程中也需要关注此处type的来源。
最后再通过下面案例来复盘整个过程,全程展示在VS里调试里通过反序列化漏洞弹出计算器。
1. 输入http://localhost:5651/Default?node=root&value=type 加载了远程的(192.168.231.135) 1.xml文件
2. 通过xmlHelper.GetValue得到root节点下的所有XML数据
3. 这步最关键,得到root节点的type属性,并提供给GetType方法,XmlSerializer对象实例化成功
4. XmlSerializer.Deserialize(xmlReader) 成功调出计算器
最后附上动图
由于XmlSerializer是系统默认的反序列类,所以在实际开发中使用率还是比较高的,攻击者发现污染点可控的时候,可以从两个维度去寻找利用的点,第一从Web应用程序中寻求可以执行命令或者写WebShell的类和方法;第二就是本文中所说的利用ObjectDataProvider、ResourceDictionary、XamlReader组成的攻击链去执行命令或者反弹Shell ,最后.NET反序列化系列课程笔者会同步到 https://github.com/Ivan1ee/ 、https://ivan1ee.gitbook.io/ ,后续笔者将陆续推出高质量的.NET反序列化漏洞文章 ,大致课程大纲如下图
欢迎大伙持续关注,交流,更多的.NET安全和技巧可关注实验室公众号或者笔者的小密圈。