Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 230 additions & 8 deletions xml/System.Web.Script.Serialization/JavaScriptTypeResolver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,236 @@
If you provide a type resolver to the <xref:System.Web.Script.Serialization.JavaScriptSerializer> instance, the serializer will use the <xref:System.Web.Script.Serialization.JavaScriptTypeResolver.ResolveTypeId%2A> and <xref:System.Web.Script.Serialization.JavaScriptTypeResolver.ResolveType%2A> methods to map between the managed type and the string value during the serialization and deserialization process, respectively.
The <xref:System.Web.Script.Serialization.JavaScriptTypeResolver> class is the base class for the <xref:System.Web.Script.Serialization.SimpleTypeResolver> class, which provides an implementation of a type resolver that uses the managed type assembly-qualified name.
> [!NOTE]
> When using a `JavaScriptTypeResolver`, the resulting JSON payload contains a special `__type` property. This property includes the full type name, including namespace, of the target type. Before using a custom resolver, verify that the full name of the target type does not contain sensitive or privileged information.
## Examples
The following example demonstrates how to create a custom type resolver.
[!code-csharp[System.Web.Script.Serialization.TypeResolver#2](~/samples/snippets/csharp/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/CS/App_Code/TypeResolver.cs#2)]
[!code-vb[System.Web.Script.Serialization.TypeResolver#2](~/samples/snippets/visualbasic/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/VB/App_Code/TypeResolver.vb#2)]
The following example shows how to create a custom `JavaScriptTypeResolver` and how to use it to serialize or deserialize an object.
```cs
using System;
using System.Linq;
using System.Web.Script.Serialization;
namespace SampleApp
{
class Program
{
static void Main(string[] args)
{
// The object array to serialize.
Person[] people = new Person[]
{
new Person()
{
Name = "Kristen Solstad",
Age = 15,
HomeAddress = new Address()
{
Street1 = "123 Palm Ave",
City = "Some City",
StateOrProvince = "ST",
Country = "United States",
PostalCode = "00000"
}
},
new Adult()
{
Name = "Alex Johnson",
Age = 39,
Occupation = "Mechanic",
HomeAddress = new Address()
{
Street1 = "445 Lorry Way",
Street2 = "Unit 3A",
City = "Some City",
Country = "United Kingdom",
PostalCode = "AA0 A00"
}
}
};
// Serialize the object array, then write it to the console.
string serializedData = SerializePeopleArray(people);
Console.WriteLine("Serialized:");
Console.WriteLine(serializedData);
Console.WriteLine();
// Now deserialize the object array.
Person[] deserializedArray = DeserializePeopleArray(serializedData);
Console.WriteLine("Deserialized " + deserializedArray.Length + " people.");
foreach (Person person in deserializedArray)
{
Console.WriteLine(person.Name + " (Age " + person.Age + ") [" + person.GetType() + "]");
}
}
static string SerializePeopleArray(Person[] people)
{
// The custom type resolver to use.
// Note: Except for primitives like int and string, *every* type that
// we might see in the object graph must be listed here.
CustomTypeResolver resolver = new CustomTypeResolver(
typeof(Person),
typeof(Adult),
typeof(Address));
// Instantiate the serializer
JavaScriptSerializer serializer = new JavaScriptSerializer(resolver);
// Serialize the object array, then return it.
string serialized = serializer.Serialize(people);
return serialized;
}
static Person[] DeserializePeopleArray(string serializedData)
{
// The custom type resolver to use.
// Note: This is the same list that was provided to the Serialize routine.
CustomTypeResolver resolver = new CustomTypeResolver(
typeof(Person),
typeof(Adult),
typeof(Address));
// Instantiate the serializer.
JavaScriptSerializer serializer = new JavaScriptSerializer(resolver);
// Deserialize the object array, then return it.
Person[] deserialized = serializer.Deserialize<Person[]>(serializedData);
return deserialized;
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address HomeAddress { get; set; }
}
public class Adult : Person
{
public string Occupation { get; set; }
}
public class Address
{
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string StateOrProvince { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
}
// A custom JavaScriptTypeResolver that restricts the payload.
// to a set of known good types.
class CustomTypeResolver : JavaScriptTypeResolver
{
private readonly Type[] _allowedTypes;
public CustomTypeResolver(params Type[] allowedTypes)
{
if (allowedTypes == null)
{
throw new ArgumentNullException("allowedTypes");
}
// Make a copy of the array the caller gave us.
_allowedTypes = (Type[])allowedTypes.Clone();
}
public override Type ResolveType(string id)
{
// Iterate over all of the allowed types, looking for a match
// for the 'id' parameter. n.b. calling Type.GetType(id) is dangerous,
// so we instead perform a match on the Type.FullName property.
foreach (Type allowedType in _allowedTypes)
{
if (allowedType.FullName == id)
{
return allowedType;
}
}
// The caller provided a type we don't recognize. This could be
// dangerous, so we'll fail the operation immediately.
throw new ArgumentException("Unknown type: " + id, "id");
}
public override string ResolveTypeId(Type type)
{
// Before we serialize data, quickly double-check to make
// sure we're allowed to deserialize the data. Otherwise it's
// no good serializing something if we can't deserialize it.
if (_allowedTypes.Contains(type))
{
return type.FullName;
}
throw new InvalidOperationException("Cannot serialize an object of type " + type + ". Did you forget to add it to the allow list?");
}
}
}
```
The preceding app outputs the following to the console, formatted for readability.
```txt
Serialized:
[
{
"__type": "SampleApp.Person",
"Name": "Kristen Solstad",
"Age": 15,
"HomeAddress": {
"__type": "SampleApp.Address",
"Street1": "123 Palm Ave",
"Street2": null,
"City": "Some City",
"StateOrProvince": "ST",
"Country": "United States",
"PostalCode": "00000"
}
},
{
"__type": "SampleApp.Adult",
"Occupation": "Mechanic",
"Name": "Alex Johnson",
"Age": 39,
"HomeAddress": {
"__type": "SampleApp.Address",
"Street1": "445 Lorry Way",
"Street2": "Unit 3A",
"City": "Some City",
"StateOrProvince": null,
"Country": "United Kingdom",
"PostalCode": "AA0 A00"
}
}
]
Deserialized 2 people.
Kristen Solstad (Age 15) [SampleApp.Person]
Alex Johnson (Age 39) [SampleApp.Adult]
```
In the preceding sample, the `Adult` type subclasses the `Person` type. A custom `JavaScriptTypeResolver` is used to include the type information as part of the generated JSON payload. This allows limited polymorphism when deserializing the JSON payload back into a .NET object graph. The payload can control whether to return a base `Person` instance or a derived `Adult` instance back to the caller.
This sample is safe because it uses an `allow-list` mechanism to control deserialization. The code:
* Initializes the `CustomTypeResolver` with an explicit list of allowed types.
* Restricts the deserialization process to only approved list of types. The restriction prevents [deserialization attacks](https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data), where the remote client specifies a malicious `__type` in the JSON payload and tricks the server into deserializing a dangerous type.
Even though the app only expects `Person` and `Adult` instances to be deserialized as part of the top-level array, it's still necessary to add `Address` to the allow-list because:
* Serializing a `Person` or `Adult` also serializes an `Address` as part of the object graph.
* All types that might be present in the object graph need to be accounted for in the allow list. Primitives like `int` and `string` do not need to be specified.
> [!WARNING]
> Do not call `Type.GetType(id)` within the `ResolveType` method. This could introduce a security vunerability into the app. Instead, iterate through the list of allowed types, comparing their `Type.FullName` property against the incoming `id`, as shown in the preceding sample:
]]></format>
</remarks>
Expand Down Expand Up @@ -99,7 +321,7 @@
## Remarks
When a type resolver is associated with a <xref:System.Web.Script.Serialization.JavaScriptSerializer> instance, the serializer uses the <xref:System.Web.Script.Serialization.JavaScriptTypeResolver.ResolveType%2A> method when it iterates through a JSON string to determine the specific managed type to which the JSON type should be converted.
]]></format>
</remarks>
<block subset="none" type="overrides">
Expand Down
31 changes: 16 additions & 15 deletions xml/System.Web.Script.Serialization/SimpleTypeResolver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,26 @@
</Base>
<Interfaces />
<Docs>
<summary>Provides a type resolver for managed types.</summary>
<summary>Provides an insecure type resolver for managed types.</summary>
<remarks>
<format type="text/markdown"><![CDATA[

## Remarks
This type resolver can be used with the <xref:System.Web.Script.Serialization.JavaScriptSerializer> class to include in the serialized JavaScript Object Notation (JSON) string the assembly-qualified name of any managed type that requires custom type metadata.

The <xref:System.Web.Script.Serialization.SimpleTypeResolver> class enables you to serialize managed types as JSON while retaining the managed type definition for custom types.

To create an instance of the <xref:System.Web.Script.Serialization.JavaScriptSerializer> class that uses a type resolver, you must use the <xref:System.Web.Script.Serialization.JavaScriptSerializer.%23ctor%28System.Web.Script.Serialization.JavaScriptTypeResolver%29> constructor.




> [!WARNING]
> The `SimpleTypeResolver` class is insecure and should not be used. Using `SimpleTypeResolver` to deserialize JSON could allow the remote client to execute malicious code within the app and take control of the web server.

See [JavaScriptTypeResolver](/dotnet/api/system.web.script.serialization.javascripttyperesolver) for a sample which demonstrates using a custom `JavaScriptTypeResolver` safely.

.NET provides source analyzers to provide alerts to usage of the dangerous `SimpleTypeResolver` type. See [Overview of source code analyzers](/visualstudio/code-quality/roslyn-analyzers-overview) for more information on source analyzers. For instructions on installing the source analyzers, see [Install .NET Compiler Platform code analyzers](/visualstudio/code-quality/install-roslyn-analyzers).

When the source analyzers package is activated in a project, references to `SimpleTypeResolver` produce one of the following compiler warnings:

- [CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver](/visualstudio/code-quality/ca2321)
- [CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing](/visualstudio/code-quality/ca2322)

## Examples
The following example demonstrates how to use the <xref:System.Web.Script.Serialization.SimpleTypeResolver> class and shows the resulting serialized strings by using different type resolvers.

[!code-aspx-csharp[System.Web.Script.Serialization.TypeResolver#1](~/samples/snippets/csharp/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/CS/Default.aspx#1)]
[!code-aspx-vb[System.Web.Script.Serialization.TypeResolver#1](~/samples/snippets/visualbasic/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/VB/Default.aspx#1)]


]]></format>
</remarks>
<altmember cref="P:System.Type.AssemblyQualifiedName" />
Expand Down Expand Up @@ -127,4 +128,4 @@
</Docs>
</Member>
</Members>
</Type>
</Type>