惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
Webroot Blog
Webroot Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
V2EX - 技术
V2EX - 技术
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
Recorded Future
Recorded Future
S
Schneier on Security
I
InfoQ
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The GitHub Blog
The GitHub Blog
S
Security @ Cisco Blogs
O
OpenAI News
W
WeLiveSecurity
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
Simon Willison's Weblog
Simon Willison's Weblog
人人都是产品经理
人人都是产品经理
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
GbyAI
GbyAI
NISL@THU
NISL@THU
T
Tailwind CSS Blog
V
Visual Studio Blog
PCI Perspectives
PCI Perspectives
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Jina AI
Jina AI
D
DataBreaches.Net
B
Blog RSS Feed
N
News and Events Feed by Topic
N
News and Events Feed by Topic
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
腾讯CDC
Latest news
Latest news
V
Vulnerabilities – Threatpost
Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
V
V2EX
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Register - Security
The Register - Security
Help Net Security
Help Net Security

博客园 - MeteorSeed

【译】利用 GitHub Actions 实现 Visual Studio 扩展构建自动化 【译】自定义 Visual Studio 的界面外观 【译】无需离开 Visual Studio 即可审阅拉取请求 【译】Visual Studio 五月更新 —— 计划、评审、优化 【译】构建前先计划:带你了解 Visual Studio 中的 Plan Agent 【译】Visual Studio 中的 Agent Skill:让 Copilot 适配团队工作模式 【译】TypeScript 7 测试版已在 Visual Studio 2026 18.6 Insiders 3 中默认启用 【译】Visual Studio 四月更新 —— Cloud Agent 集成 【译】Azure MCP 工具现已内置集成至 Visual Studio 2022,无需额外安装扩展 【译】告别繁琐查错:认识下新的 Visual Studio Debugger Agent Workflow 【译】在 Visual Studio 中完全掌控您的悬浮窗口 【译】Bookmark Studio:在 Visual Studio 中实现书签功能升级 【译】Visual Studio 三月更新 —— 打造专属自定义 Agent 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【译】让开发流程更高效:为 Visual Studio 订阅用户解锁 Syncfusion 【译】第一时间获取 Visual Studio 订阅的独家资讯,直达您的收件箱 【译】Visual Studio Dev Essentials:面向每位开发者的免费实用工具 【译】Visual Studio 二月更新 【译】Visual Studio 中的 Agent:内置 Agent 和自定义 Agent 【译】Visual Studio 一月更新 —— 增强的编辑器体验 【MongoDB】MongoDB 概述 【译】Copilot 记忆 【译】Visual Studio 2026 来了:更快、更智能,深受老用户的喜爱 【译】AI 是如何解决我的拖延症的 【译】使用 Visual Studio 2026 简化您的 Git 工作流程 【译】借助 Copilot Chat 新推出的 URL 上下文功能,释放互联网的力量 【译】Visual Studio 11 月更新 —— Visual Studio 2026、Cloud Agent Preview 及更多内容 【译】Copilot Profiler Agent —— 分析任务交由 AI,应用性能不受影响 【译】Visual Studio —— 为现代开发的速度而打造 【译】在 Visual Studio 2026 中,减少升级时间,增加编码时间 【译】Azure MCP 服务器现已内置到 Visual Studio 2026 中:智能体工作流的新时代 【译】初探 Visual Studio 2026 全新的用户体验
Newtonsoft.Json 与 System.Text.Json 多态反序列化的安全性差异解析
MeteorSeed · 2025-12-18 · via 博客园 - MeteorSeed

  多态反序列化是处理继承结构对象序列化的常见需求,但不同 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 规则的警示

20251218

  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}");
            }
        }
    }
}

    运行结果为:

20251218

  通过 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 字段类型的合法性,仅允许安全类型。

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

me