news 2026/5/22 8:37:49

Newtonsoft.Json 与 System.Text.Json 多态反序列化的安全性差异解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Newtonsoft.Json 与 System.Text.Json 多态反序列化的安全性差异解析

多态反序列化是处理继承结构对象序列化的常见需求,但不同 JSON 序列化库的实现机制差异会带来显著的安全风险。微软 CA2326 规则明确警示:避免使用非安全的 JsonSerializerSettings 配置(如 Newtonsoft.Json 的 TypeNameHandling 非 None 值),否则可能引发类型注入攻击。本文将对比 Newtonsoft.Json 与 System.Text.Json 在多态反序列化中的实现差异,重点分析安全性问题,并通过代码实例验证两者的安全表现。

多态反序列化的实现机制差异

Newtonsoft.Json:基于TypeNameHandling 的灵活设计

Newtonsoft.Json 通过 TypeNameHandling 配置项控制是否在 JSON 中嵌入类型元数据。当设置 TypeNameHandling 支持多态时,JSON 会携带 $type 字段(包含类型的完全限定名和程序集信息),反序列化时直接根据该字段实例化对应类型。这种设计虽然灵活支持多态,但缺乏默认的类型校验机制,攻击者可构造包含恶意类型的 JSON,触发敏感类型实例化。

System.Text.Json:多态配置的安全设计

System.Text.Json 默认不支持多态反序列化,需通过 [JsonDerivedType] 特性或 DerivedTypes 显式声明允许的派生类型。反序列化时仅处理配置过的类型,拒绝未授权的类型注入,从机制上规避了安全风险。

CA2326 规则的警示

CA2326 规则的核心是禁止使用 TypeNameHandling 非 None 值的配置 —— 攻击者可利用 $type 字段构造恶意 JSON,实例化如 ProcessStartInfo(执行系统命令)、FileStream(读写文件)等敏感类型,引发远程代码执行或数据泄露。

using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Diagnostics; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; namespace NewtonsoftSecurityDemo { [JsonPolymorphic(TypeDiscriminatorPropertyName = "CustomerType")] [JsonDerivedType(typeof(PaymentCompletedEvent), "PaymentCompletedEvent")] [JsonDerivedType(typeof(OrderCreatedEvent), "OrderCreatedEvent")] public class TransactionEvent { public string EventId { get; set; } = Guid.NewGuid().ToString(); public DateTime EventTime { get; set; } = DateTime.Now; public string OrderId { get; set; } // 业务扩展字段(攻击者利用的入口) public object ExtData { get; set; } } public class PaymentCompletedEvent : TransactionEvent { public decimal Amount { get; set; } public string PaymentMethod { get; set; } } public class OrderCreatedEvent : TransactionEvent { public string UserId { get; set; } public int ItemCount { get; set; } } // Newtonsoft.Json 安全绑定器(演示白名单校验) public class EventSerializationBinder : ISerializationBinder { // 仅允许的安全类型白名单 private readonly HashSet<string> _allowedTypes = new() { "NewtonsoftSecurityDemo.PaymentCompletedEvent", "NewtonsoftSecurityDemo.OrderCreatedEvent", //"System.Diagnostics.ProcessStartInfo" }; public Type BindToType(string assemblyName, string typeName) { // 仅允许白名单内的类型 if (!_allowedTypes.Contains(typeName)) { throw new NotSupportedException($"禁止反序列化未授权类型:{typeName}"); } return Type.GetType($"{typeName}, {assemblyName}") ?? typeof(TransactionEvent); } public void BindToName(Type serializedType, out string? assemblyName, out string? typeName) { assemblyName = serializedType.Assembly.FullName; typeName = serializedType.FullName; } } class Program { static void Main(string[] args) { Console.WriteLine("=== Newtonsoft.Json 命令执行攻击演示 ==="); Newtonsoft_Attack_ProcessStartInfo(); Console.WriteLine("\n=== Newtonsoft.Json 文件读取攻击演示 ==="); Newtonsoft_Attack_FileStream(); Console.WriteLine("\n=== Newtonsoft.Json 启用 SerializationBinder:安全防护演示 ==="); Newtonsoft_Secure_WithBinder(); Console.WriteLine("\n=== System.Text.Json 安全防护演示 ==="); SystemTextJson_Defense(); Console.ReadKey(); } /// <summary> /// 模拟:注入ProcessStartInfo执行系统命令 /// </summary> static void Newtonsoft_Attack_ProcessStartInfo() { string maliciousCallbackJson = @$" {{ ""$type"": ""NewtonsoftSecurityDemo.PaymentCompletedEvent, NewtonsoftSecurityDemo"", ""EventId"": ""{Guid.NewGuid()}"", ""OrderId"": ""ORD_{new Random().Next(1000, 9999)}"", ""Amount"": 999.00, ""PaymentMethod"": ""Alipay"", ""ExtData"": {{ ""$type"": ""System.Diagnostics.ProcessStartInfo,System.Diagnostics.Process"", ""FileName"": ""cmd.exe"", ""Arguments"": ""/c echo 'some scripts' > C:\temp\attack_log.txt && echo 'doing' >> C:\temp\attack_log.txt"", ""UseShellExecute"": true }} }}"; var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, }; var eventData = Newtonsoft.Json.JsonConvert.DeserializeObject<TransactionEvent>(maliciousCallbackJson, settings); Console.WriteLine($"处理订单事件:{eventData.OrderId}"); if (eventData.ExtData is ProcessStartInfo psi) { Directory.CreateDirectory("C:\temp"); Process.Start(psi); Console.WriteLine($" [攻击成功] 执行命令:{psi.Arguments}"); Console.WriteLine($" [攻击结果] 生成文件:C:\temp\attack_log.txt 文件内容:"); if (File.Exists("C:\temp\attack_log.txt")) { string content = File.ReadAllText("C:\temp\attack_log.txt"); Console.WriteLine($"{content}"); } } } /// <summary> /// 模拟:注入FileInfo读取敏感文件 /// </summary> static void Newtonsoft_Attack_FileStream() { string targetFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "appsettings.json"); if (!File.Exists(targetFile)) { File.WriteAllText(targetFile, "ConnectionString: 123456"); } string maliciousExportJson = @$" {{ ""$type"": ""NewtonsoftSecurityDemo.OrderCreatedEvent, NewtonsoftSecurityDemo"", ""OrderId"": ""ORD_{new Random().Next(1000, 9999)}"", ""UserId"": ""user_{new Random().Next(100, 999)}"", ""ExtData"": {{ ""$type"": ""System.IO.FileInfo"", ""FileName"": ""{targetFile.Replace("\", "\\")}"" }} }}"; var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }; var eventData = Newtonsoft.Json.JsonConvert.DeserializeObject<TransactionEvent>(maliciousExportJson, settings); Console.WriteLine($"处理订单导出:{eventData.OrderId}"); // 通过FileInfo读取文件内容(模拟攻击逻辑) if (eventData.ExtData is FileInfo fileInfo) { using (var sr = new StreamReader(fileInfo.OpenRead())) { string sensitiveContent = sr.ReadToEnd(); Console.WriteLine($" [攻击成功] 读取敏感文件内容:\n{sensitiveContent}"); } } } /// <summary> /// Newtonsoft.Json 启用SerializationBinder:拦截恶意类型 /// </summary> static void Newtonsoft_Secure_WithBinder() { string maliciousCallbackJson = @$" {{ ""$type"": ""NewtonsoftSecurityDemo.PaymentCompletedEvent, NewtonsoftSecurityDemo"", ""EventId"": ""{Guid.NewGuid()}"", ""OrderId"": ""ORD_{new Random().Next(1000, 9999)}"", ""Amount"": 999.00, ""PaymentMethod"": ""Alipay"", ""ExtData"": {{ ""$type"": ""System.Diagnostics.ProcessStartInfo,System.Diagnostics.Process"", ""FileName"": ""cmd.exe"", ""Arguments"": ""/c echo 'some scripts' > C:\temp\attack_log.txt && echo 'doing' >> C:\temp\attack_log.txt"", ""UseShellExecute"": true }} }}"; var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, SerializationBinder = new EventSerializationBinder() // 启用白名单校验 }; try { var eventData = Newtonsoft.Json.JsonConvert.DeserializeObject<TransactionEvent>(maliciousCallbackJson, settings); if (eventData.ExtData is ProcessStartInfo) { Console.WriteLine(" [防护失效] 恶意类型未被拦截(异常)"); } } catch (Exception ex) { Console.WriteLine($" [防护成功] 拦截未授权类型:{ex.Message}"); } } /// <summary> /// System.Text.Json 安全防护验证 /// </summary> static void SystemTextJson_Defense() { string maliciousCallbackJson = @$" {{ ""CustomerType"": ""PaymentCompletedEvent"", ""EventId"": ""{Guid.NewGuid()}"", ""OrderId"": ""ORD_{new Random().Next(1000, 9999)}"", ""Amount"": 999.00, ""PaymentMethod"": ""Alipay"", ""ExtData"": {{ ""$type"": ""System.Diagnostics.ProcessStartInfo,System.Diagnostics.Process"", ""FileName"": ""cmd.exe"", ""Arguments"": ""/c echo 'some scripts' > C:\temp\attack_log.txt && echo 'doing' >> C:\temp\attack_log.txt"", ""UseShellExecute"": true }} }}"; var eventData = System.Text.Json.JsonSerializer.Deserialize<TransactionEvent>(maliciousCallbackJson); Console.WriteLine($" 主对象类型:{eventData.GetType().FullName}"); Console.WriteLine($" ExtData 实际类型:{eventData.ExtData.GetType().FullName}"); if (eventData.ExtData is JsonElement) { Console.WriteLine(" [防护成功] 恶意类型ProcessStartInfo被拦截,ExtData仅保留原始JSON结构,未反序列化为恶意对象"); } else if (eventData.ExtData is ProcessStartInfo) { Console.WriteLine(" [防护失效] 恶意类型解析成功"); } else { Console.WriteLine($" [正常业务] 解析到合法类型:{eventData.ExtData.GetType().FullName}"); } Console.WriteLine("\n尝试转换ExtData为ProcessStartInfo:"); try { var psi = (ProcessStartInfo)eventData.ExtData; Console.WriteLine(" [防护失效] 恶意类型解析成功"); } catch (InvalidCastException ex) { Console.WriteLine($" [防护成功] 强制转换失败,原因:{ex.Message}"); } } } }

运行结果为:

通过 Demo 可以发现:

- Newtonsoft 无防护时攻击成功;

- Newtonsoft 启用 SerializationBinder 后拦截了恶意类型;

- System.Text.Json 始终拦截恶意类型,ExtData 为 JsonElement,无法转换为 ProcessStartInfo。

为什么 Newtonsoft.Json 启用 SerializationBinder 可降低风险?

先看代码:

public class EventSerializationBinder : ISerializationBinder { // 仅允许的安全类型白名单 private readonly HashSet<string> _allowedTypes = new() { "NewtonsoftSecurityDemo.PaymentCompletedEvent", "NewtonsoftSecurityDemo.OrderCreatedEvent", //"System.Diagnostics.ProcessStartInfo" }; public Type BindToType(string assemblyName, string typeName) { // 仅允许白名单内的类型 if (!_allowedTypes.Contains(typeName)) { throw new NotSupportedException($"禁止反序列化未授权类型:{typeName}"); } return Type.GetType($"{typeName}, {assemblyName}") ?? typeof(TransactionEvent); } public void BindToName(Type serializedType, out string? assemblyName, out string? typeName) { assemblyName = serializedType.Assembly.FullName; typeName = serializedType.FullName; } }

SerializationBinder 的核心作用是:接管从 JSON 中的 $type 字符串 到实际 Type 类型的映射过程,强制校验类型合法性。简单说:

- 无 SerializationBinder:反序列化器会无条件反射创建 $type 指定的任意类型,包括危险类型;

- 有 SerializationBinder:反序列化器必须经过你的自定义校验逻辑,仅允许白名单内的类型被实例化,直接阻断恶意类型的创建。

小结

Newtonsoft.Json 的 TypeNameHandling 机制虽灵活,但易被利用触发安全漏洞;System.Text.Json 通过显式多态配置白名单的设计,规避了类型注入风险。

在实际开发中,针对多态场景,建议优先使用 System.Text.Json。若必须使用 Newtonsoft.Json,需遵循以下安全实践:

- 避免使用 TypeNameHandling 非 None 值。

- 若必须启用,需严格校验 $type 字段类型的合法性,仅允许安全类型。

我希望您喜欢这篇文章,并一如既往地感谢您阅读并与朋友和同事分享我的文章。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/22 3:34:53

中国辅助驾驶“新竞赛”打响,高智价比AI芯片如何定义新标杆?

中国辅助驾驶的落地竞速已经从单纯的性能比拼&#xff0c;进入“法规与体验双轮驱动”的全新阶段。一方面&#xff0c;包括中国在内的全球多个国家和地区已对车辆搭载AEB系统提出强制性要求&#xff0c;直接推动了组合辅助驾驶进入市场主导的“爆发期”。根据《高工智能汽车研究…

作者头像 李华
网站建设 2026/5/21 10:48:44

渔人的直感:FF14终极智能钓鱼计时器完全指南

渔人的直感&#xff1a;FF14终极智能钓鱼计时器完全指南 【免费下载链接】Fishers-Intuition 渔人的直感&#xff0c;最终幻想14钓鱼计时器 项目地址: https://gitcode.com/gh_mirrors/fi/Fishers-Intuition 还在为错过幻海流最佳时机而烦恼吗&#xff1f;是否经常因为分…

作者头像 李华
网站建设 2026/5/20 22:30:02

如何在30天内完成Agent有效性验证?生物药研发加速的秘密武器

第一章&#xff1a;生物制药Agent有效性验证的挑战与机遇在生物制药领域&#xff0c;智能Agent正逐步应用于药物发现、靶点识别和临床试验模拟等关键环节。然而&#xff0c;这些Agent的有效性验证面临诸多挑战&#xff0c;同时也孕育着重大技术突破的机遇。数据异质性与标准化难…

作者头像 李华
网站建设 2026/5/20 10:33:28

为什么顶尖金融机构都在重构Agent审计日志?背后隐藏的4大合规趋势

第一章&#xff1a;金融合规 Agent 的审计日志在金融行业&#xff0c;系统操作的可追溯性是合规性的核心要求之一。审计日志作为记录系统行为的关键组件&#xff0c;为监管审查、异常检测和责任追溯提供了数据基础。金融合规 Agent 通过自动化手段收集、分析并归档关键操作事件…

作者头像 李华
网站建设 2026/5/21 11:16:12

58、Linux与Ubuntu:深入解析与使用指南

Linux与Ubuntu:深入解析与使用指南 1. mkinitrd工具介绍 mkinitrd工具会查看 /etc/fstab 、 /etc/modprobe.conf 和 /etc/raidtab 文件,以获取在启动过程中确定需要加载哪些模块所需的信息。在我们的系统中,使用以下命令: # mkinitrd initrd-2.6.7-1.img 2.6.7-1…

作者头像 李华
网站建设 2026/5/20 21:17:07

60、Ubuntu安装规划与硬件要求全解析

Ubuntu安装规划与硬件要求全解析 1. 选择Ubuntu的优势与安装关键因素 Ubuntu作为一种计算解决方案有诸多优势,由于其丰富的软件资源,它能在不同层级和硬件平台上扮演多种角色。为确保安装成功,需关注以下关键因素: - 准备工作 :充分讨论迁移或部署事宜,明确其带来的…

作者头像 李华