在 ASP.NET MVC Core 2 中使用 MetadataPropertyHandling 模型绑定 JSON 数据

Posted

技术标签:

【中文标题】在 ASP.NET MVC Core 2 中使用 MetadataPropertyHandling 模型绑定 JSON 数据【英文标题】:Model binding JSON data with MetadataPropertyHandling in ASP.NET MVC Core 2 【发布时间】:2018-09-02 09:22:50 【问题描述】:

我已经为此苦苦挣扎了一段时间,我正在尝试将客户端 JSON 帖子绑定到 ASP.NET Core 2 MVC 中的模型,但我无法获取要采用的值。我发布的 JSON 数据如下。


    "$type": "Namespace.ApplicationLoggingModel, Projectname.Web",
    "Test": "works"

...和模型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Namespace.Models

    public class ApplicationLoggingModel
    
        public string Test  get; set; 
    

控制器动作似乎知道它是ApplicationLoggingModel 类,但它不会绑定测试数据。我也尝试过下面的 JSON,但这也不起作用。


    "$Type": "Namespace.ApplicationLoggingModel, Projectname.Web",
    "$Values": [
        "$Type": "System.String, mscorlib",
        "Test": true
    ]

我也尝试过“$type”和“$values”,但它似乎不区分大小写,所以我有点卡住了。这在 .NET Core 和模型绑定中是否可行?我不得不更改项目和命名空间名称,如果您需要更多信息,请告诉我。

更新:我在下面添加了控制器操作,尝试了常规模型绑定后,似乎 $type 不起作用,因为我已经添加了 [FromBody] 标记以使常规模型绑定工作。该模型现在正在通过 null。

[HttpPost("Save/id")]
public ActionResult Save(Guid id, [FromBody]ApplicationLoggingModel model)

    return RedirectToAction("Entities", Guid.Empty);

编辑:正如 dbc 正确指出的那样,我的 ApplicationLoggingModel 不是多态的,因此不需要 TypeNameHandling - ApplicationLoggingModel 将包含一组多态模型,我基本上已经退后一步尝试让一个这样工作在我可以在其他模型上实现它之前。

【问题讨论】:

既然你的ApplicationLoggingModel 不是多态的,为什么你还需要TypeNameHandling?这样做可能会带来安全风险,请参阅例如How to configure Json.NET to create a vulnerable web API 和 TypeNameHandling caution in Newtonsoft Json. 它将包含一组多态模型,我基本上已经退后一步尝试让一个组件工作,然后才能在其他模型上实现它。我将编辑我的帖子以指出这一点。 然后,奇怪的是,前面提到的How to configure Json.NET to create a vulnerable web API 似乎展示了如何使其工作。在该示例中,存在多态属性而不是多态根模型。另外,启动时需要配置options.SerializerSettings.TypeNameHandling 顺便问一下,您的多态模型数组中的模型是否派生自某个公共基类或接口? 它们派生自一个抽象类,我还没有看到这部分是否有效,它是由另一个开发人员编写的。还要感谢您的文章,将 options.SerializerSettings.TypeNameHandling = TypeNameHandling.All 添加到 AddJsonOptions 已解决了该问题。我必须解决安全问题,因为我很确定这个功能不是我可以解决的 【参考方案1】:

如How to configure Json.NET to create a vulnerable web API 所示,您可以在整个对象图中的反序列化和模型绑定期间启用TypeNameHandling,方法是:

services.AddMvc().AddJsonOptions(options =>

    options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
);

Startup.cs.

但是,这样做会带来安全风险,正如那篇文章以及 TypeNameHandling caution in Newtonsoft Json 中所述。因此,除非您创建自定义 ISerializationBinder 以过滤掉不需要或意外的类型,否则我不会推荐此解决方案。

作为这种有风险的解决方案的替代方案,如果您只需要使根模型列表具有多态性,则可以使用以下方法:

    从应用程序中定义的一些通用基类或接口派生出所有多态模型(即不是一些系统类型,例如CollectionBaseINotifyPropertyChanged)。

    使用List<T> 类型的单个属性定义容器DTO,其中T 是您的常用基本类型。

    [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)] 标记该属性。

    不要不要在启动时设置options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;

要了解这在实践中是如何工作的,假设您有以下模型类型层次结构:

public abstract class ModelBase



public class ApplicationLoggingModel : ModelBase

    public string Test  get; set; 


public class AnotherModel : ModelBase

    public string AnotherTest  get; set; 

然后按如下方式定义您的根 DTO:

public class ModelBaseCollectionDTO

    [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
    public List<ModelBase> Models  get; set; 

那么如果你如下构造它的一个实例并序列化为JSON:

var dto = new ModelBaseCollectionDTO

    Models = new List<ModelBase>()
    
        new ApplicationLoggingModel  Test = "test value" ,
        new AnotherModel  AnotherTest = "another test value" ,
    ,
;

var json = JsonConvert.SerializeObject(dto, Formatting.Indented);

生成以下 JSON:


  "Models": [
    
      "$type": "Namespace.Models.ApplicationLoggingModel, TestApp",
      "Test": "test value"
    ,
    
      "$type": "Namespace.Models.AnotherModel, TestApp",
      "AnotherTest": "another test value"
    
  ]

然后可以将其反序列化回ModelBaseCollectionDTO,而不会丢失类型信息,也无需全局设置ItemTypeNameHandling

var dto2 = JsonConvert.DeserializeObject<ModelBaseCollectionDTO>(json);

工作示例fiddle。

但是,如果我尝试How to configure Json.NET to create a vulnerable web API 中显示的攻击如下:

try

    File.WriteAllText("rce-test.txt", "");
    var badJson = JToken.FromObject(
        new
        
            Models = new object[] 
            
                new FileInfo("rce-test.txt")  IsReadOnly = false ,
            
        ,
        JsonSerializer.CreateDefault(new JsonSerializerSettings  TypeNameHandling = TypeNameHandling.Auto, Formatting = Formatting.Indented ));
    ((JObject)badJson["Models"][0])["IsReadOnly"] = true;
    Console.WriteLine("Attempting to deserialize attack JSON: ");
    Console.WriteLine(badJson);
    var dto2 = JsonConvert.DeserializeObject<ModelBaseCollectionDTO>(badJson.ToString());
    Assert.IsTrue(false, "should not come here");

catch (JsonException ex)

    Assert.IsTrue(!new FileInfo("rce-test.txt").IsReadOnly);
    Console.WriteLine("Caught expected 0: 1", ex.GetType(), ex.Message);

那么文件rce-test.txt标记为只读,而是抛出以下异常:

Newtonsoft.Json.JsonSerializationException:JSON“System.IO.FileInfo,mscorlib”中指定的类型与“Namespace.Models.ModelBase,Tile”不兼容。路径 'Models[0].$type',第 4 行,位置 112。

表明攻击小工具FileInfo 甚至从未构造过。

注意事项:

通过使用 TypeNameHandling.Auto,您可以避免使用非多态属性的类型信息使 JSON 膨胀。

可以通过在开发过程中对预期结果ModelBaseCollectionDTO 进行测试序列化来确定帖子正文中JSON 的正确格式。

有关攻击失败原因的说明,请参阅External json vulnerable because of Json.Net TypeNameHandling auto?。只要没有攻击小工具与您的基本模型类型兼容(可分配给),您就应该是安全的。

因为您没有在启动时设置TypeNameHandling,所以您不会使您的其他API 容易受到攻击,或者通过深度嵌套的多态属性(例如提到的here)使您自己受到攻击。

李>

不过,为了提高安全性,您可能仍想创建一个自定义 ISerializationBinder

【讨论】:

以上是关于在 ASP.NET MVC Core 2 中使用 MetadataPropertyHandling 模型绑定 JSON 数据的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core 2,使用没有 MVC 的 Razor 页面单击按钮

007.Adding a view to an ASP.NET Core MVC app -- 在asp.net core mvc中添加视图

ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 14. ASP.NET Core Identity 入门

[MVC&Core]ASP.NET Core MVC 视图传值入门

基础教程:ASP.NET Core 2.0 MVC筛选器

ASP.NET Core Web 应用程序系列- 在ASP.NET Core中使用Autofac替换自带DI进行批量依赖注入(MVC当中应用)