-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
@gewarren, @Rick-Anderson, we received a report that some of our docs related to Full Framework's JavaScriptSerializer
contain insecure code samples. I've provided below some updated contents for the two pages. Would you be able to wordsmith and merge them in to the relevant pages when you find some time? Thanks!
This is not time-critical.
SimpleTypeResolver
https://docs.microsoft.com/dotnet/api/system.web.script.serialization.simpletyperesolver
Please add a prominently displayed warning:
Warning: The
SimpleTypeResolver
class is dangerous and should not be used. UsingSimpleTypeResolver
to deserialize JSON could allow the remote client to execute malicious code within your app and take control of your web server.
Please delete the sample code that accompanies this type. We want to discourage use of this type and don't want any code that can be copied + pasted.
Please delete the contents of the Remarks section and replace with the following:
See the JavaScriptTypeResolver
class documentation for a sample which demonstrates using a custom JavaScriptTypeResolver
safely.
.NET provides source analyzers to help alert you to usage of the dangerous SimpleTypeResolver
type within your code base. See "Overview of source code analyzers" for more information on source analyzers. For instructions on installing the source analyzers into your project, see "Install .NET Compiler Platform code analyzers".
When the source analyzers package is activated in your project, references to SimpleTypeResolver
will produce one of the following compiler warnings in your source.
- CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver
- CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing
JavaScriptTypeResolver
https://docs.microsoft.com/dotnet/api/system.web.script.serialization.javascripttyperesolver
Please replace the contents of the Examples section with the following.
The following example shows how to create a custom JavaScriptTypeResolver
and how to use it to serialize or deserialize an object.
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?");
}
}
}
This example application will output the following to the console (formatted for readability).
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 this 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 application code initializes the CustomTypeResolver
with an explicit list of allowed types, and the deserialization process is restricted to only that approved list of types. This prevents deserialization attacks, 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 is still necessary to add Address
to the allow-list. This is because serializing a Person
or Adult
also serializes an Address
as part of the object graph, and 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.)
Caution: Do not call
Type.GetType(id)
within yourResolveType
method. This could introduce a security hole into your app. Instead, iterate through the list of allowed types, comparing theirType.FullName
property against the incoming id, as shown in this sample.
Please append the following notice to the Remarks section.
Note: When using a
JavaScriptTypeResolver
, the resulting JSON payload will contain a special __type property. This property includes the full type name - including namespace - of the target type. Before using a custom resolver, double-check that the full name of your target type does not contain sensitive or privileged information.