# 8、SoapFormatter反序列化漏洞

## 0x00 前言

SoapFormatter格式化器和下节课介绍的BinaryFormatter格式化器都是.NET内部实现的序列化功能的类，SoapFormatter直接派生自System.Object，位于命名空间System.Runtime.Serialization.Formatters.Soap，并实现IRemotingFormatter、IFormatter接口，用于将对象图持久化为一个SOAP流，SOAP是基于XML的简易协议，让应用程序在HTTP上进行信息交换用的。但在某些场景下处理了不安全的SOAP流会造成反序列化漏洞从而实现远程RCE攻击，本文笔者从原理和代码审计的视角做了相关介绍和复现。

## 0x01 SoapFormatter序列化

SoapFormatter类实现的IFormatter接口中定义了核心的Serialize方法可以非常方便的实现.NET对象与SOAP流之间的转换，可以将数据保存为XML文件，官方提供了两个构造方法。

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJcxMZHgvqXYa90QcP%2Fimage.png?alt=media\&token=f99b0a5c-c79c-4498-a9cf-33edeaf9b8a6)

下面还是用老案例来说明问题，首先定义TestClass对象

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJd0G27JJxHZ4B9VUe%2Fimage.png?alt=media\&token=a1e0a338-88fb-4618-b0d3-a635786ee8c1)

定义了三个成员，并实现了一个静态方法ClassMethod启动进程。 序列化通过创建对象实例分别给成员赋值

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJd3zLk_j0889-fkqv%2Fimage.png?alt=media\&token=b5cf7dd7-57b0-48f5-a233-6f65449188af)

常规下使用Serialize得到序列化后的SOAP流，通过使用XML命名空间来持久化原始程序集，例如下图TestClass类的开始元素使用生成的xmlns进行限定，关注a1 命名空间

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdAmjcQnX4b2wevil%2Fimage.png?alt=media\&token=ce8025b0-7c1e-40a0-93d0-05588d600a04)

## 0x02 SoapFormatter反序列化

### 2.1、反序列化用法

SoapFormatter类反序列化过程是将SOAP消息流转换为对象，通过创建一个新对象的方式调用Deserialize多个重载方法实现的，查看定义得知实现了IRemotingFormatter、IFormatter接口，

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdI0hSPODLiBl5jF3%2Fimage.png?alt=media\&token=4bc696c7-4d5a-4052-9dee-fe9f6ac184ef)

查看IRemotingFormatter接口定义得知也是继承了IFormatter

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdLx-d3lO7fBUtrWu%2Fimage.png?alt=media\&token=bf0705da-dfac-4d15-8a9a-96a5c41d292e)

笔者通过创建新对象的方式调用Deserialize方法实现的具体实现代码可参考以下

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdPoY0p_kr2dITXp-%2Fimage.png?alt=media\&token=86a1a8d3-c2d3-490e-b5f3-146c491fff6e)

反序列化后得到TestClass类的成员Name的值。

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdUw-NNHi11y7x9wk%2Fimage.png?alt=media\&token=b6e3d41d-fdf2-453f-bb8b-0d0cb0fe1017)

### 2.2、攻击向量—ActivitySurrogateSelector

在SoapFormatter类的定义中除了构造函数外，还有一个SurrogateSelector属性， SurrogateSelector便是代理选择器，序列化代理的好处在于一旦格式化器要对现有类型的实例进行反序列化，就调用由代理对象自定义的方法。查看得知实现了ISurrogateSelector接口，定义如下

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdZyXko13CgW3RTua%2Fimage.png?alt=media\&token=92cd3900-c131-427d-b3a3-dee1d04cd11a)

因为序列化代理类型必须实现System.Runtime.Serialization.ISerializationSurrogate接口，ISerializationSurrogate在Framework Class Library里的定义如下：

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdcgFwRpbH4V3l6TV%2Fimage.png?alt=media\&token=1d839f1f-8e06-4734-a629-ddae116bbb29)

图中的GetObjectData方法在对象序列化时进行调用，目的将值添加到SerializationInfo集合里，而SetObjectData方法用于反序列化，调用这个方法的时候需要传递一个SerializationInfo对象引用，**换句话说就是使用SoapFormatter类的Serialize方法的时候会调用GetObjectData方法，使用Deserialize会调用SetObjectData方法。**&#x53;oapFormatter类还有一个非常重要的属性SurrogateSelector，定义如下

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdh-nIDpg869j935v%2Fimage.png?alt=media\&token=0a982b4a-546b-44d6-9906-569c8de8d295)

在序列化对象的时候如果属性SurrogateSelector属性的值非NULL便会以这个对象的类型为参数调用其GetSurrogate方法，如果此方法返回一个有效的对象ISerializationSurrogate，这个对象对找到的类型进行反序列化，这里就是一个关键的地方，我们要做的就是实现重写ISerializationSurrogate调用自定义代码，如下Demo

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdkcKRn-NJeM_tUQk%2Fimage.png?alt=media\&token=054f53ba-f8e9-40f8-9879-cb416a2bcbab)

代码中判断类型解析器IsSerializable属性是否可用，如果可用直接基类返回，如果不可用就获取派生类 System.Workflow\.ComponentModel.Serialization.ActivitySurrogateSelector的类型，然后交给Activator创建实例再回到GetObjectData方法体内，另外为了对序列化数据进行完全控制，就需要实现Serialization.ISeralizable接口，定义如下：

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdq0YHFdKZbXkaCt5%2Fimage.png?alt=media\&token=644bf8a0-b362-40cc-88ef-2bc571b312b6)

有关更多的介绍请参考《.NET高级代码审计第二课 Json.Net反序列化漏洞》，在实现自定义反序列类的时通过构造方法读取攻击者提供的PocClass类

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdtzlV6UPQngh_5R1%2Fimage.png?alt=media\&token=a85fb161-a789-4e16-944a-317786924aa8)

下图定义了PayloadClass类实现ISerializable接口，然后在GetObjectData方法里又声明泛型List集合接收byte类型的数据

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJdxiBt-saI432wg6L%2Fimage.png?alt=media\&token=98e4addc-a168-45bf-8dbe-a54addac2a82)

将PocClass对象添加到List集合，声明泛型使用IEnumerable集合map\_type接收程序集反射得到的Type并返回IEnumerable类型，最后用Activator.CreateInstance创建实例保存到 e3此时是一个枚举集合的迭代器。

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJe06VbkaCpGp-YgMD%2Fimage.png?alt=media\&token=daa3231a-b267-4a03-b16b-74cec8a0d7a7)

上图将变量e3填充到了分页控件数据源，查看PageDataSource类定义一目了然，

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJe2du5U-jnp4ncDQN%2Fimage.png?alt=media\&token=b0ef830d-d786-48e9-ad5d-faefa92f3027)

除此之外System.Runtime.Remoting.Channels.AggregateDictionary返回的类型支持IDictionary，然后实例化对象DesignerVerb并随意赋值，此类主要为了配合填充MenuCommand类properties属性的值，最后为哈希表中的符合条件的buckets赋值。

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJe7QPq4t2Nl7BmMaG%2Fimage.png?alt=media\&token=f0517feb-04ef-49f9-bab3-314f7a6d8e59)

接下来用集合添加数据源DataSet，DataSet和DataTable对象继承自System.ComponentModel.MarshalByValueComponent类，可序列化数据并支持远程处理ISerializable接口，这是ADO.NET对象中仅有支持远程处理的对象，并以二进制格式进行持久化。

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJeBXpiupdkMTCCEGP%2Fimage.png?alt=media\&token=7d58a96e-54f6-42e9-a619-622e36ed6e62)

更改属性DataSet.RemotingFormat值为SerializationFormat.Binary，更改属性DataSet.CaseSensitive为false等，再调用BinaryFormatter序列化List集合，如下图。

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJeFS9oYrIyH2e8PJE%2Fimage.png?alt=media\&token=69b7bff6-d030-4dc1-a881-4686a530dcf3)

因为指定了RemotingFormat属性为Binary，所以引入了BinaryFormatter格式化器并指定属性SurrogateSelector代理器为自定义的MySurrogateSelector类。序列化后得到SOAP-XML，再利用SoapFormatter对象的Deserialize方法解析读取文件内容的流数据，成功弹出计算器

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJeJpsYoQLE6uLLOBS%2Fimage.png?alt=media\&token=0e5a354e-2963-434d-85bb-d914a8074511)

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJeOVX0veotPZ87alp%2Fimage.png?alt=media\&token=fba21d58-1ec7-4371-a207-21a99dbbc9ff)

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJePgtVhGVA8H7gwqC%2Fimage.png?alt=media\&token=97abce7e-dd41-4a1a-949c-b5816a27e6f0)

### 2.3、攻击向量—PSObject

由于笔者的Windows主机打过了CVE-2017-8565（Windows PowerShell远程代码执行漏洞）的补丁，利用不成功，所以在这里不做深入探讨，有兴趣的朋友可以自行研究。有关于补丁的详细信息参考：

{% embed url="<https://support.microsoft.com/zh-cn/help/4025872/windows-powershell-remote-code-execution-vulnerability>" %}

## 0x03 代码审计视角

### 3.1、XML载入

从代码审计的角度找到漏洞的EntryPoint，传入XML，就可以被反序列化，这种方式也是很常见的，需要关注一下，LoadXml直接载入xml数据，这个点也可以造成XXE漏洞。例如这段代码：

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJebHPJ51Cs_ZGcErn%2Fimage.png?alt=media\&token=a1510f88-f741-44da-b4bc-530a4fc66581)

这种污染点漏洞攻击成本很低，攻击者只需要控制传入字符串参数source便可轻松实现反序列化漏洞攻击，弹出计算器。

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJefS40h04kC1qDT9F%2Fimage.png?alt=media\&token=d4a32952-a919-4330-9069-46dc1c7a8d70)

### 3.2、File读取

![](https://716521924-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LZrIoIxZUs7eJuu-0Hd%2F-LdJJiSH2kgk6ZTgNDlg%2F-LdJejVzUEGsIcGzVWP5%2Fimage.png?alt=media\&token=8a06a414-0300-4d43-a752-8d32b909ef9d)

这段是摘自某个应用的代码片段，在审计的时候只需要关注DeserializeSOAP方法中传入的path变量是否可控.

## 0x04 总结

实际开发中SoapFormatter 类从.NET Framework 2.0开始，这个类已经渐渐过时了，开发者选择它的概率也越来越少，官方注明用BinaryFormatter来替代它，下篇笔者接着来介绍BinaryFormatter反序列化漏洞。最后.NET反序列化系列课程笔者会同步到 <https://github.com/Ivan1ee/> 、<https://ivan1ee.gitbook.io/> ，后续笔者将陆续推出高质量的.NET反序列化漏洞文章，欢迎大伙持续关注，交流，更多的.NET安全和技巧可关注实验室公众号。
