使用 Protobuf-net 确保反序列化数据的安全性

信息安全 。网 tcp
2021-08-31 11:46:04

我有兴趣使用 protobuf-net 序列化/反序列化通过我的 TCP 应用程序发送的数据。几周前我了解了 protobuf-net,并从这里下载了它,并计划在我的网络应用程序中使用它。尽管我以前使用过 XML 序列化程序,但我从未考虑过序列化中的安全问题,因为我创建的程序是私有的,并且不会通过网络发送数据。

在阅读了一些关于Data Security Considerations 的内容后,我了解到,粗心的序列化实现可能会导致自定义客户端向服务器发送恶意数据,这是一个主要的安全漏洞。

但是,由于我不完全了解序列化/反序列化对象的过程,也不完全了解 的功能.net,我不知道应该采取哪些预防措施来确保攻击者找不到任何方法服务器以意想不到的方式行事。在确定我的代码中已经采取了所有必要的预防措施之前,我不想实现序列化。

我了解攻击者可能构成的威胁包括:

  1. 无意的信息泄露。我不确定这一点,但如果我没记错的话,这只是发件人的安全问题,而不是接收端的问题。只要发送的机密数据是加密的,这不应该是一个问题吗?

  2. 拒绝服务。这可以通过限制被反序列化的对象的大小来停止。

  3. 恶意代码执行。这是我最担心的可能性,尤其是由于我对序列化和.net.

    是唯一的对象的序列化变量(私有和公共)吗?所以事件和函数不能以任何方式传递?由于函数不能在 VB 中设置为变量(不像其他语言,如具有闭包的 Swift),这不应该是一个问题,但我不完全确定是否有另一种方法来规避这个问题,这就是我问的原因。

    Object如果是上述情况,那么如果我仅将数据反序列化为没有“潜在危险”功能的类或类,是否可以确保没有恶意代码运行?

当一个对象被反序列化时,被New调用?如果是这样,那么我必须确保New打电话是“安全的”?

除了上述之外,攻击者是否有任何其他方式可以破坏我的服务器(例如,利用继承或事件/处理程序)?

上述问题的答案对于除 之外的序列化程序是否相同protobuf-net


从 SO 移出,因为它被认为更适合这里

3个回答

我无法回答问题#1 或#2,但我可以对问题#3 说一点。首先,快速概述 protobuf-net。

在线上,Google 的协议缓冲区是一种消息格式而不是序列化格式。消息由键/值对序列组成,其中值可以是原始数据类型或嵌套序列。它有点类似于JSON,除了键是整数而不是名称字符串该格式与语言无关(因此不包含特定于语言的类型信息),并且具有足够的自文档性,可以跳过意外属性的值,并且可以推断原始值的数据类型。

protobuf-net是一个基于合约的 .NET 类型序列化程序,可从协议缓冲区格式序列化到协议缓冲区格式。“基于契约”意味着序列化程序在消息属性和 .Net 属性之间构建一个映射(也称为“契约”),然后使用该契约来构造和反序列化 .Net 对象。要反序列化的“根”对象由反序列化调用指定,例如

var rootObject = ProtoBuf.Serializer.Deserialize<RootObjectType>(stream)

可以在此处此处找到其使用的基本说明。

protubuf-net 仅有限地使用不安全代码 - 我相信目前仅用于浮点值的有效反序列化。这可以通过使用FEAT_SAFE定义的构建来禁用。因此,格式错误的消息覆盖堆或堆栈的可能性被最小化。Protobuf-net 不与Google 实现的协议缓冲区共享代码库。

那么,protobuf-net 是如何构造它的合约(the RuntimeTypeModel)来将 .Net 类型映射到消息的呢?由于消息键是整数而不是字符串,因此使用 .Net 公共属性或字段名称的规范选择不起作用。相反,有以下选项:

  1. 该类型可以使用[ProtoContract][ProtoMember()]属性进行注释,例如文档中的示例:

    [ProtoContract]
    class Person {
        [ProtoMember(1)]
        public string Name {get;set:}
        [ProtoMember(2)]
        public Address Address {get;set;}
    }
    [ProtoContract]
    class Address {
        [ProtoMember(1)]
        public string Line1 {get;set;}
        [ProtoMember(2)]
        public string Line2 {get;set;}
    } 
    

    标记为的属性和字段[ProtoMember]将使用反射发现并序列化。其他人不会。

  2. 可以以编程方式构建合约,如此处所示

  3. 默认情况下,protobuf-net 合同生成是可选的。即,仅对指定的属性和字段进行序列化。但是,可以通过设置ProtoContractAttribute.ImplicitFields为以下之一来覆盖它:

    public enum ImplicitFields
    {
        /// <summary>
        /// No members are serialized implicitly; all members require a suitable
        /// attribute such as [ProtoMember]. This is the recmomended mode for
        /// most scenarios.
        /// </summary>
        None = 0,
        /// <summary>
        /// Public properties and fields are eligible for implicit serialization;
        /// this treats the public API as a contract. Ordering beings from ImplicitFirstTag.
        /// </summary>
        AllPublic= 1,
        /// <summary>
        /// Public and non-public fields are eligible for implicit serialization;
        /// this acts as a state/implementation serializer. Ordering beings from ImplicitFirstTag.
        /// </summary>
        AllFields = 2
    }
    
  4. protobuf-net 还可以使用数据合约序列化器和XmlSerializer属性来自动创建合约。这可以通过属性禁用ProtoContractAttribute.UseProtoMembersOnly

  5. 虽然 protobuf-net 最初设计为代码优先序列化程序(您定义类型,然后定义如何序列化它们),但它确实支持从消息原型生成 c# 代码。有关详细信息,请参见此处

因此,如您所见,在序列化期间注入不需要的类型和代码的可能性受到设计的限制,因为合同而不是消息控制构造对象的类型。这可以通过以下建议进一步限制:

  1. 不要使用[ProtoMember(DynamicType = true)].

    protobuf-net 实现了对标准的扩展,允许反序列化 .Net 类型以在消息本身中指定 - 破坏了开箱即用标准的相对安全性。默认情况下禁用,但可以通过设置启用DynamicType = true不要使用此功能,因为这是在反序列化期间注入意外对象的最明显方式。

    如果您担心团队中的其他开发人员可能会使用此功能(因为它确实使序列化多态类型变得非常容易),您可以TypeModel.DynamicTypeFormatting在序列化程序尝试解析动态类型或动态类型时设置一个引发异常的事件输入名称,例如:

        ProtoBuf.Meta.TypeFormatEventHandler handler = (o, e) => { throw new NotSupportedException("Dynamic typing is not allowed."); };
        ProtoBuf.Meta.RuntimeTypeModel.Default.DynamicTypeFormatting += handler;
    

    请务必在您实际使用的类型模型上设置事件,正如此处指出的那样。

  2. 小心继承。protobuf-net 确实支持继承注意不要让不需要的派生类型潜入合约。例如,给定以下内容:

    [ProtoContract]
    [ProtoInclude(7, typeof(ValidatedSqlQuery))]
    [ProtoInclude(8, typeof(DebugSqlCommand))]
    public abstract class SqlCommand
    {
    }
    
    /// <summary>
    /// Represents a fully validated, parameterized SQL query
    /// </summary>
    [ProtoContract]
    public class ValidatedSqlQuery
    {
    }
    
    /// <summary>
    /// For testing purposes, execute any command against the database.
    /// </summary>
    [ProtoContract]
    class DebugSqlCommand
    {
    }
    

    a 有可能通过适当的消息DebugSqlCommand在 a 中构建,这可能会导致损坏。List<SqlCommand>

  3. 序列化属性,而不是字段。不要使用ImplicitFields.AllFields. ImplicitFields.AllPublic尽可能避免使用。

  4. 验证所有属性设置器中的数据。如果您不想对“普通”设置器进行验证,则可以使用特定于序列化的设置器,例如:

    [ProtoContract]
    class Contact {
        public string Email { get; set; }
    
        [ProtoMember(1)]
        string SerializedEmail {  
            get { return Email; }
            set
            {
                // Make sure the email is well-formed, throw an exception if not
                ValidateEmail(value);
                Email = value;
            }
        }
    }
    

    [OnDeserialized]在复杂的验证场景中,您可以在回调中验证多个相关属性。

  5. 最后,您的反序列化代码仅与您反序列化的对象一样安全!不要序列化包含或使用不安全代码的对象。不允许序列化调试对象。

问题不是序列化。它是服务器上的反序列化。

不谨慎的反序列化会在代码中创建一个新对象。一个对象可能包含各种信息,其中一些可能是可执行的。因此,流氓客户端可以序列化并发送包含程序代码的信息,然后服务器会不假思索地执行这些代码。坏消息!

如果您从外部客户端接收任何数据,则必须始终在对其进行任何操作之前对其进行审查。在这种情况下,您需要确保对象中不包含任何函数,或者任何此类函数永远无法运行。您还需要确保任何剩余的数据都是您期望的形式并遵循您期望的任何约束(例如字符串长度、数字大小等)。您必须先完成所有这些,然后才能信任数据并在应用程序代码中对其进行处理。

使用开放标准在这里也有帮助。例如,使用 JSON 将提供某种程度的保证,即内容无法执行。XML 会类似但更冗长。您可以使用现有的工具来验证数据,从而节省您的工作量并且几乎可以肯定更强大。

澄清更新:由于我对协议缓冲区格式缺乏了解,我担心我在这里所说的答案比你原来的问题更通用。要回答您的最后一个问题,是的,其他序列化程序可能会有所不同,请参阅我的评论以及此答案。

根据要求:原始缓冲区的链接:

https://github.com/google/protobuf/issues/760

http://blog.codeclimate.com/blog/2014/06/05/choose-protocol-buffers/

我使用的谷歌搜索: protobuf-net security issues

1#如果数据被正确加密,则信息不会透露给第三方,但信息当然会透露给接收者。

2# 是的,如果您谈论的是大型对象,DoS 可能是一个问题。

3#一般反序列化不执行代码。反序列化时构造函数不运行(默认除外),但是有反序列化回调可能会做一些愚蠢的事情。请检查您的对象的所有父类(您正在反序列化)它们是否做愚蠢的事情。

例如,一个愚蠢的事情是

public void OnDeserialized(...) { deleteFile(someMember); }

你不能像我认为的那样反序列化为“对象”。即使编译时类型是,运行时类型也不会是对象。