使用ASP.NET MVC Core 2中的MetadataPropertyHandling模型绑定JSON数据 [英] Model binding JSON data with MetadataPropertyHandling in ASP.NET MVC Core 2
问题描述
我已经为此苦苦挣扎了一段时间,我正在尝试将客户端JSON帖子绑定到ASP.NET Core 2 MVC中的模型,但是我无法获取这些值.我要发布的JSON数据如下.
I've been struggling with this for a little while now, I'm trying to bind a client-side JSON post to a model in ASP.NET Core 2 MVC but I can't get the values to take. The JSON data I'm posting is below.
{
"$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,但这也不起作用.
The controller action seems to know it's ApplicationLoggingModel
class, but it won't bind the test data. I've also tried the JSON below but that doesn't work either.
{
"$Type": "Namespace.ApplicationLoggingModel, Projectname.Web",
"$Values": [{
"$Type": "System.String, mscorlib",
"Test": true
}]
}
我也尝试过"$ type"和"$ values",但是它似乎不区分大小写,所以我有点卡住了.在.NET Core和模型绑定中,这是否有可能?我不得不更改项目和名称空间的名称,因此,如果您需要更多信息,请告诉我.
I've also tried "$type" and "$values" but it doesn't appear to be case sensitive, so I'm a little bit stuck. Is this even possible in .NET Core and with model binding? I've had to change the project and namespace names so let me know if you need any more information.
更新:我已经在下面添加了控制器操作,尝试了常规模型绑定,因为自从添加[FromBody]
标签以使常规模型绑定开始工作以来,$ type似乎不起作用.该模型现在为空.
UPDATE: I've added the controller action below, having tried regular model binding it seems like the $type isn't working as I've since added the [FromBody]
tag to get the regular model binding to work. The model is now coming through null.
[HttpPost("Save/{id}")]
public ActionResult Save(Guid id, [FromBody]ApplicationLoggingModel model)
{
return RedirectToAction("Entities", Guid.Empty);
}
正如dbc正确指出的那样,我的ApplicationLoggingModel不是多态的,因此不需要TypeNameHandling-ApplicationLoggingModel将包含一个多态模型的数组,我基本上已经在这里退后了一步,以尝试并实现此功能在其他模型上实现它之前.
as dbc rightly pointed out, my ApplicationLoggingModel isn't polymorphic and therefore doesn't need TypeNameHandling - ApplicationLoggingModel will contain an array of polymorphic Models, I've basically taken a step back here to try and get one this working before I can implement it on the other models.
推荐答案
As shown in How to configure Json.NET to create a vulnerable web API, you can enable TypeNameHandling
during deserialization and model binding throughout your entire object graph by doing
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
});
在Startup.cs
中.
但是,这样做可能会引入该文章以及Newtonsoft Json中的 TypeNameHandling警告中所述的安全风险 a> .因此,除非您创建自定义 ISerializationBinder
过滤掉不必要或意外的类型.
However, doing this can introduce security risks as described in that very article as well as TypeNameHandling caution in Newtonsoft Json. As such I would not recommend this solution unless you create a custom ISerializationBinder
to filter out unwanted or unexpected types.
作为此风险解决方案的替代方法,如果仅需要使根模型列表具有多态性,则可以使用以下方法:
As an alternative to this risky solution, if you only need to make your list of root models be polymorphic, the following approach can be used:
-
从应用程序中定义的某些常见基类或接口派生所有多态模型(即不是某些系统类型,例如
INotifyPropertyChanged
).
Derive all of your polymorphic models from some common base class or interface defined in your application (i.e. not some system type such as
CollectionBase
orINotifyPropertyChanged
).
使用单个属性List<T>
类型定义容器 DTO ,其中T
是您的常用基本类型.
Define a container DTO with single property of type List<T>
where T
is your common base type.
使用 [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
标记该属性.
Mark that property with [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
.
在启动时不设置options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
.
要了解其在实际中的工作原理,请说您具有以下模型类型层次结构:
To see how this works in practice, say you have the following model type hierarchy:
public abstract class ModelBase
{
}
public class ApplicationLoggingModel : ModelBase
{
public string Test { get; set; }
}
public class AnotherModel : ModelBase
{
public string AnotherTest { get; set; }
}
然后按如下所示定义您的根DTO:
Then define your root DTO as follows:
public class ModelBaseCollectionDTO
{
[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
public List<ModelBase> Models { get; set; }
}
然后,如果您按照以下方式构造它的实例并序列化为JSON:
Then if you construct an instance of it as follows and serialize to 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
:
This can then be deserialized back into a ModelBaseCollectionDTO
without loss of type information and without needing to set ItemTypeNameHandling
globally:
var dto2 = JsonConvert.DeserializeObject<ModelBaseCollectionDTO>(json);
示例工作小提琴.
However, if I attempt the attack shown in How to configure Json.NET to create a vulnerable web API as follows:
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
标记为 not 为只读,并引发以下异常:
Then the file rce-test.txt
is not marked as read-only, and instead the following exception is thrown:
Newtonsoft.Json.JsonSerializationException:JSON'System.IO.FileInfo,mscorlib'中指定的类型与'Namespace.Models.ModelBase,Tile'不兼容.路径"Models [0].$ type",第4行,位置112.
Newtonsoft.Json.JsonSerializationException: Type specified in JSON 'System.IO.FileInfo, mscorlib' is not compatible with 'Namespace.Models.ModelBase, Tile'. Path 'Models[0].$type', line 4, position 112.
表明攻击小工具FileInfo
从未被构建.
Indicating that the attack gadget FileInfo
is never even constructed.
注意:
-
使用
TypeNameHandling.Auto
可以避免使用非多态属性的类型信息使JSON膨胀.
By using
TypeNameHandling.Auto
you avoid bloating your JSON with type information for non-polymorphic properties.
在发布正文中JSON的正确格式可以通过在开发过程中对预期的结果ModelBaseCollectionDTO
进行序列化测试来确定.
The correct format for the JSON in your post body can be determined by test-serializing the expected resulting ModelBaseCollectionDTO
during development.
有关攻击失败原因的解释,请参见由于Json.Net TypeNameHandling自动而易受攻击的外部json? .只要没有攻击小工具与您的基本模型类型兼容(可分配),您就应该是安全的.
For an explanation of why the attack fails see External json vulnerable because of Json.Net TypeNameHandling auto?. As long as no attack gadget is compatible with (assignable to) your base model type, you should be secure.
因为您没有在启动时设置TypeNameHandling
,所以不会使其他API容易受到攻击,也不会通过深度嵌套的多态属性(例如提到的
Because you do not set TypeNameHandling
in startup you are not making your other APIs vulnerable to attack or exposing yourself to attacks via deeply nested polymorphic properties such as those mentioned here.
尽管如此,为了增加安全性,您可能仍想创建自定义的ISerializationBinder
.
Nevertheless, for added safety, you may still want to create a custom ISerializationBinder
.
这篇关于使用ASP.NET MVC Core 2中的MetadataPropertyHandling模型绑定JSON数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!