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

The*_*mon 2 c# security interface json.net json-deserialization

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

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

我们的公司名称位于我们使用的每个命名空间的开头,因此在下面的代码中,我们只需检查 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
});
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 5

风险在于TypeNameHandling,攻击者可能会欺骗接收者构建攻击小工具- 一种类型的实例,在构建、填充或处置时会对接收系统造成攻击。有关概述,请参阅

如果您打算通过要求所有反序列化类型位于您自己公司的 .Net 命名空间中来防止此类攻击,请注意,当使用 进行序列化时TypeNameHandling.All"$type"所有数组和对象(包括 .网络类型如List<T>。因此,您必须在"$type"可能出现类型信息的任何地方应用检查。最简单的方法是使用自定义序列化绑定器,如下所示:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

可以如下使用:

var settings = new JsonSerializerSettings
{
    SerializationBinder = new MySerializationBinder(),
    TypeNameHandling = TypeNameHandling.All,
};
Run Code Online (Sandbox Code Playgroud)

这具有比您的解决方案性能更高的额外优势,因为JObject不再需要预先加载到 a 中。

然而,这样做后,您可能会遇到以下问题:

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

    序列化TypeNameHandling.ObjectsTypeNameHandling.Auto可以减少将此类无害的系统类型列入白名单的需要,因为与 相比,此类系统类型的类型信息在序列化期间不太可能包含在内TypeNameHandling.All

    为了进一步简化类型检查并减少总体攻击面,您可以考虑仅允许根对象上的类型信息。为此,请参阅json.net - 如何仅在根对象上添加属性 $typeSuppressItemTypeNameContractResolver来自接受的答案可以在接收端和发送端使用,以忽略非根对象的类型信息。

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

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

    由于您的根对象似乎都实现了某个接口,因此IFoo您将序列化和反序列化一个接口Root<IFoo>,这会将可能的攻击小工具的空间限制为实现的类IFoo- 一个更小的攻击面。

    演示小提琴在这里

  2. 反序列化泛型时,外部泛型和内部泛型参数类型可能都需要递归清理。例如,如果您的命名空间包含 ,Generic<T>则检查 typeName 是否以您公司的命名空间开头将无法防止通过Generic<SomeAttackGadget>.

  3. 即使您只允许来自您自己的命名空间的类型,也很难说这足够安全,因为我们不知道您自己的命名空间中的任何类是否可能被重新用作攻击工具。