具有基本命名空间检查的 Newtonsoft TypeNameHandling.all 安全吗?

is Newtonsoft TypeNameHandling.all with a basic namespace check safe?

在我们的 API 上,我们需要接收 json,将其反序列化为一个接口,设置一个字段,然后将其发送出去。为此,我在两端都设置了 jsonConvert 以使用 TypeNameHandling.All。有问题的端点应该被相当锁定,但总有可能有人获得访问权限并将 $type 设置为系统 class 使用危险的构造函数或垃圾收集方法。

我的问题是在尝试反序列化之前澄清类型的命名空间是否足够安全?或者是否仍然存在在 json 中使用带有危险 class 类型的子对象之类的风险?如果仍然存在我错过的风险或漏洞利用,我还可以采取哪些其他措施来减轻危险?

我们的公司名称位于我们使用的每个命名空间的开头,因此在下面的代码中,我们只需检查 json 中设置的类型是否以我们的公司名称开头。开头的 {} 只是让编译器知道它不需要在检查后将 JObject 保留在内存中。

{ //check the type is valid
    var securityType = JsonConvert.DeserializeObject<JObject>(request.requestJson);
    JToken type;
    if (securityType.TryGetValue("$type", out type))
    {
        if (!type.ToString().ToLower().StartsWith("foo")) { //'foo' is our company name, all our namespaces start with foo
            await logError($"Possible security violation, client tried  to instantiate {type}", clientId: ClientId);
            throw new Exception($"Request type {type} not supported, please use an IFoo");
        }
    }
    else
    {
        throw new Exception("set a type...");
    }
}
IFoo requestObject = JsonConvert.DeserializeObject<IFoo>(request.requestJson, new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.All
});

TypeNameHandling 的风险在于,攻击者可能会诱使接收方构建 攻击小工具 - 一个在构建、填充或处置时会产生影响的类型的实例对接收系统的攻击。有关概述,请参阅

如果您要通过要求所有反序列化类型都在您自己公司的 .Net 名称中来防止此类攻击space,请注意,在使用 TypeNameHandling.All、[=71] 进行序列化时=]"$type" 信息将出现在整个 JSON 令牌层次结构中,适用于所有数组和对象(包括 .Net 类型,例如 List<T>)。因此,您必须在可能出现类型信息的任何地方应用您的 "$type" 检查。最简单的方法是使用 custom serialization binder,如下所示:

public class MySerializationBinder : DefaultSerializationBinder
{
    const string MyNamespace = "foo"; //'foo' is our company name, all our namespaces start with foo

    public override Type BindToType(string assemblyName, string typeName)
    {
        if (!typeName.StartsWith(MyNamespace, StringComparison.OrdinalIgnoreCase))
            throw new JsonSerializationException($"Request type {typeName} not supported, please use an IFoo");
        var type = base.BindToType(assemblyName, typeName);
        return type;
    }
}

可以这样使用:

var settings = new JsonSerializerSettings
{
    SerializationBinder = new MySerializationBinder(),
    TypeNameHandling = TypeNameHandling.All,
};

因为不再需要预加载到 JObject,所以这比您的解决方案更高效。

但是,这样做之后,您可能会遇到以下问题:

  1. 即使根对象始终来自您公司的名称space,嵌套值的 "$type" 属性也不一定在您的公司名称中space .具体来说,将包括 List<T>Dictionary<TKey, Value> 等无害通用系统集合以及数组的类型信息。您可能需要增强 BindToType() 以将此类类型列入白名单。

    使用 TypeNameHandling.ObjectsTypeNameHandling.Auto 进行序列化可以减少将此类无害系统类型列入白名单的需要,因为与 [=14] 相比,此类系统类型的类型信息不太可能在序列化过程中被包含=].

    为了进一步简化类型检查并减少总体攻击面,您可以考虑只允许根对象上的类型信息。为此,请参阅 SuppressItemTypeNameContractResolver 已接受的答案可用于接收方和发送方,以忽略非根对象的类型信息。

    或者,您可以使用 TypeNameHandling.None 全局序列化和反序列化,并将您的根对象包装在标有 [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)] 的容器中,如下所示:

    public class Root<TBase>
    {
        [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]
        public TBase Data { get; set; }
    }
    

    因为你的根对象似乎都实现了一些接口 IFoo 你会序列化和反序列化一个 Root<IFoo> 这将限制 space 可能的攻击工具到 类实施 IFoo -- 攻击面小得多。

    演示 fiddle here.

  2. 反序列化泛型时,可能需要递归清理外部泛型和内部泛型参数类型。例如,如果您的名称space包含一个Generic<T>,那么检查类型名称以您公司的名称space开头将无法防止通过Generic<SomeAttackGadget>.

  3. 即使你只允许使用你自己名字的类型space,也很难说这就足够安全了,因为我们不知道 类 以您自己的名义space 可能会被重新用作攻击工具。