是否可以使用受信任类型的标记来防止 Json.Net TypeNameHandling 漏洞?

问题描述

我正在使用不同于 TypeNameHandling.None 的设置通过 Json.Net 阅读 vulnerability of deserializing types。 Json.Net 文档建议实现自定义 SerializationBinder。根据已知类型列表检查类型的自定义绑定器的一个简单示例是 given here

虽然这个解决方案确实有效,但已知类型集在我的场景中并不固定,因为应用程序必须支持扩展,这些扩展可能定义自己的数据类。一种解决方案是在扩展注册期间扩展已知类型列表,但是,我想到了第二种方法,我想验证一下:

我想为可信类型定义一个通用接口:

(dbc 建议:可以使用自定义属性代替标记界面。)

public interface ITrustedType { }

然后,任何需要由应用程序或扩展程序(反)序列化的类型都可以实现此接口:

public class Car : ITrustedType
{
   public IList<Passenger> Passengers { get; set; }
   // ...
}

public class Passenger : ITrustedType
{
   // ...
}

自定义绑定器不会检查类型列表,而是确保每个(反)序列化的类型都实现了受信任的类型接口,如果它实现或失败则应用认行为't。

在可枚举类型的情况下,它将确保泛型类型参数是可信类型。

这是我目前测试过的一个简单实现:

public class TrustedTypesBinder : DefaultSerializationBinder
{
    static bool IsCollectionType(Type type)
    {
        return type.GetInterfaces().Any(s => s.Namespace == "System.Collections.Generic" && s.Name.StartsWith("IEnumerable`"));
    }

    static public bool IsTrustedType(Type type)
    {
        if (IsCollectionType(type))
        {
            return type.GenericTypeArguments.All(t => IsTrustedType(t));
        }
        return typeof(ITrustedType).IsAssignableFrom(type);
    }

    public override Type BindToType(string assemblyName,string typeName)
    {
        Type type = base.BindToType(assemblyName,typeName);
        if (!IsTrustedType(type))
            throw new JsonSerializationException(typeName + " is not a trusted type.");
        return type;
    }

    public override void BindToName(Type serializedType,out string assemblyName,out string typeName)
    {
        if (!IsTrustedType(serializedType))
            throw new JsonSerializationException(serializedType.FullName + " is not a trusted type.");
        base.BindToName(serializedType,out assemblyName,out typeName);
    }
}

我怀疑此解决方案是安全的,因为应用程序或扩展程序集外部的任何类型都不会实现此接口,因此无法从恶意 JSON 输入构建攻击小工具。

我是否遗漏了任何攻击媒介?你知道有什么方法可以破坏它吗?

编辑: 我知道,任何人都可以实现这个 ITrustedType 接口并赋予它一些任意行为。据我所知,这与运行时(反)序列化的上下文无关。应用程序本身以及扩展程序都来自受信任的来源,因此当然,实现的类型是受控制的,并且攻击者不可能将任何新类型添加到已发布的应用程序二进制文件中。

此问题专门针对实例化在运行时存在且可能用于恶意目的的类型。

编辑 2: 根据 Ben Voigts 的建议,将 ICollection 更改为 IEnumerable。

解决方法

当您遇到未标记的类型时,仅检查其泛型类型参数是不够的,您需要检查每个公共属性的类型和公共构造函数的每个参数,因为这些是序列化足迹。

例如,即使 System.Data.TypedTableBase<T> 是安全的,您也确实不想允许反序列化 T,因为它具有允许配置数据库访问的公共属性。