8

Watching this video on json deserialization attacks and it shows this bit of json that can be used to trigger arbitrary code execution on any application that deserializes it.

Using ObjectDataProvider to execute arbitrary code

Now in my applications, I never even use typed json. I always deserialize to dynamic objects or JObjects. I didn't even know about the $type property until another unrelated conversation this morning.

Is there a way in my json settings to tell it never to write nor read this property? It's not something I would ever want.

George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • 1
    `"$type"` information is only written when [`TypeNameHandling`](https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm) is modified to something other than [`TypeNameHandling.None`](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_TypeNameHandling.htm) -- **which is the default**. If you never change the value, `"$type"` information is never omitted. Similarly `"$type"` properties are ignored on deserialization when `TypeNameHandling = TypeNameHandling.None`. – dbc Jan 08 '18 at 23:38
  • 1
    That being said, if a *collection* was serialized by another application using `TypeNameHandling.Arrays` then there will be an extra level of nesting in the JSON. To strip it when deserializing, see `IgnoreCollectionTypeConverter` from [Strategies for migrating serialized Json.NET document between versions/formats](https://stackoverflow.com/a/34166777/3744182) or `IgnoreArrayTypeConverter` from [make Json.NET ignore $type if it's incompatible](https://stackoverflow.com/a/33163163/3744182). – dbc Jan 08 '18 at 23:43
  • 1
    Finally, if you are working with a 3rd party library that sets `TypeNameHandling` in attributes, you can disable that with a custom contract resolver as shown in [How to disable TypeNameHandling when specified in attributes by using JsonSerializerSettings in Json.NET?](https://stackoverflow.com/q/46925930/3744182). – dbc Jan 08 '18 at 23:45
  • @dbc imo your comments are exhaustive enough to be the answer! – zaitsman Jan 08 '18 at 23:59
  • For additional examples of security risks when using `TypeNameHandling` see [TypeNameHandling caution in Newtonsoft Json](https://stackoverflow.com/q/39565954/3744182). – dbc Jan 09 '18 at 00:36

2 Answers2

11

"$type" information is only written when TypeNameHandling is modified to something other than TypeNameHandling.None -- which is the default. If you never change the value, "$type" information is never emitted.

Similarly "$type" properties are ignored on deserialization when TypeNameHandling = TypeNameHandling.None (which is, again, the default), as is stated in the docs:

// for security TypeNameHandling is required when deserializing
Stockholder newStockholder =
  JsonConvert.DeserializeObject<Stockholder>(jsonTypeNameAuto, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto
});

If nothing in your code (or in class libraries used by your code) ever modifies TypeNameHandling to something other than TypeNameHandling.None (either via settings or attributes such as JsonPropertyAttribute.TypeNameHandling) then that code execution attack cannot work. (For more precise details on usages of Json.NET's serializer that are and are not vulnerable to this attack, see Alvaro Muñoz & Oleksandr Mirosh's blackhat paper.

Also note that, if you are parsing with JToken.Parse() (or some similar static method like JObject.Parse()) rather than deserializing with JsonSerializer.Deserialize<T>() then the presence of "$type" properties will simply result in such properties getting populated into the JToken hierarchy, since JToken.Parse() never invokes the serializer. If you nevertheless want to strip those"$type" properties after parsing, you can use JsonExtensions.RemoveTypeMetadata(this JToken root) from Deserialize string that was serialized with TypeNameHandling.All to do just that.

That being said, if a collection was serialized by another application using TypeNameHandling.Arrays or TypeNameHandling.All then there will be an extra level of nesting in the JSON. To strip it when deserializing, see IgnoreCollectionTypeConverter from Strategies for migrating serialized Json.NET document between versions/formats or IgnoreArrayTypeConverter from Make Json.NET ignore $type if it's incompatible.

Finally, if you are working with a 3rd party library that sets TypeNameHandling in attributes, you can disable that with a custom contract resolver as shown in How to disable TypeNameHandling when specified in attributes by using JsonSerializerSettings in Json.NET?.

And if you're really concerned that somebody else in your team might enable TypeNameHandling, you could create a custom ISerializationBinder that throws an exception whenever an attempt is made to resolve a type or type name:

public class DisallowSerializationBindingBinder : ISerializationBinder
{
 #region ISerializationBinder Members

 public void BindToName(Type serializedType, out string assemblyName, out string typeName)
 {
  throw new JsonSerializationException("Binding of subtypes has been disabled");
 }

 public Type BindToType(string assemblyName, string typeName)
 {
  throw new JsonSerializationException("Binding of subtypes has been disabled");
 }

  #endregion
}

Then set it in JsonSerializerSettings as follows:

var settings = new JsonSerializerSettings
{
    SerializationBinder = new DisallowSerializationBindingBinder(),
};

And modify the settings globally as shown in Set default global json serializer settings (for a console app), How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? (for ASP.NET Web API) or JsonSerializerSettings and Asp.Net Core (for asp.net core).

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    I want to have `TypeNameHandling` set to `All`/`Objects`, but I still want to be able to have specific properties/types skipped, so that `$type` is not generated for them. – Shimmy Weitzhandler Mar 05 '18 at 04:44
  • 1
    @Shimmy - You could start with [this answer](https://stackoverflow.com/questions/2254872/using-json-net-converters-to-deserialize-properties/6303853#6303853) for specific properties or [this answer](https://stackoverflow.com/a/39442074/3744182) and [this one](https://stackoverflow.com/questions/42996144/typenamehandling-with-attribute-on-a-class/43036371#4303637) for specific types, using `TypeNameHandling.None` in the appropriate place. You might want to ask another question if you need something specific. – dbc Mar 05 '18 at 04:55
  • Thanks for your quick reply! I hope it's going to work. – Shimmy Weitzhandler Mar 05 '18 at 05:34
  • I was wondering if I could avoid having to decorate my Json. I am currently deserialising a pretty complex objects and I have to pollute my Jsons with things such as $type: "System.Collections.Generic.List`1[[ClassName, Namespace]], mscorlib". I am setting TypeNameHandling.All. Not doing those 2 things prevents the deserialisation from working properly. Any tip or pointer would be welcome. – progLearner Apr 27 '21 at 08:21
  • @progLearner - if you don't want the `$type` properties, you could create custom converters to test the incoming JSON to see which subtype it matches. See: [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182). – dbc Apr 27 '21 at 13:30
  • Very useful @dbc. From you link, what if new GalleryAlbum() needed to be passed an object as argument at construction? Do yo have to create all the nested classes manually? – progLearner Apr 27 '21 at 13:50
  • @progLearner - you should ask a new question and include a [mcve]. Things can become more complex if your data model uses parameterized constructors. – dbc Apr 27 '21 at 13:51
  • @dbc, maybe you can help then. Here is what I am trying to do: https://stackoverflow.com/questions/67280335/using-custom-contractresolver-or-jsonconverter-to-deserialize-complex-nested-obj?noredirect=1#67280335 – progLearner Apr 27 '21 at 13:53
2

Unfortunately TypeNameHandling.None is ignored. But you can use:

public static JsonSerializerSettings JsonSerializationSettings
        = new JsonSerializerSettings
{
    MetadataPropertyHandling = MetadataPropertyHandling.Ignore
};
Vlad
  • 29
  • 2