Skip to content

Commit 2a0a389

Browse files
Security update for JavaScriptTypeResolver and SimpleTypeResolver (#4563)
* Security update for JavaScriptTypeResolver and SimpleTypeResolver * Security update for JavaScriptTypeResolver and SimpleTypeResolver * cleanup * cleanup * cleanup * Apply suggestions from code review Co-authored-by: Genevieve Warren <[email protected]> * Update xml/System.Web.Script.Serialization/JavaScriptTypeResolver.xml Co-authored-by: Genevieve Warren <[email protected]> Co-authored-by: Genevieve Warren <[email protected]>
1 parent 0624f6a commit 2a0a389

File tree

2 files changed

+245
-22
lines changed

2 files changed

+245
-22
lines changed

xml/System.Web.Script.Serialization/JavaScriptTypeResolver.xml

Lines changed: 230 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,236 @@
3131
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.
3232
3333
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.
34-
35-
36-
34+
35+
> [!NOTE]
36+
> 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.
37+
3738
## Examples
38-
The following example demonstrates how to create a custom type resolver.
39-
40-
[!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)]
41-
[!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)]
39+
40+
The following example shows how to create a custom `JavaScriptTypeResolver` and how to use it to serialize or deserialize an object.
41+
42+
```cs
43+
using System;
44+
using System.Linq;
45+
using System.Web.Script.Serialization;
46+
47+
namespace SampleApp
48+
{
49+
class Program
50+
{
51+
static void Main(string[] args)
52+
{
53+
// The object array to serialize.
54+
Person[] people = new Person[]
55+
{
56+
new Person()
57+
{
58+
Name = "Kristen Solstad",
59+
Age = 15,
60+
HomeAddress = new Address()
61+
{
62+
Street1 = "123 Palm Ave",
63+
City = "Some City",
64+
StateOrProvince = "ST",
65+
Country = "United States",
66+
PostalCode = "00000"
67+
}
68+
},
69+
new Adult()
70+
{
71+
Name = "Alex Johnson",
72+
Age = 39,
73+
Occupation = "Mechanic",
74+
HomeAddress = new Address()
75+
{
76+
Street1 = "445 Lorry Way",
77+
Street2 = "Unit 3A",
78+
City = "Some City",
79+
Country = "United Kingdom",
80+
PostalCode = "AA0 A00"
81+
}
82+
}
83+
};
84+
85+
// Serialize the object array, then write it to the console.
86+
string serializedData = SerializePeopleArray(people);
87+
Console.WriteLine("Serialized:");
88+
Console.WriteLine(serializedData);
89+
Console.WriteLine();
90+
91+
// Now deserialize the object array.
92+
Person[] deserializedArray = DeserializePeopleArray(serializedData);
93+
Console.WriteLine("Deserialized " + deserializedArray.Length + " people.");
94+
foreach (Person person in deserializedArray)
95+
{
96+
Console.WriteLine(person.Name + " (Age " + person.Age + ") [" + person.GetType() + "]");
97+
}
98+
}
99+
100+
static string SerializePeopleArray(Person[] people)
101+
{
102+
// The custom type resolver to use.
103+
// Note: Except for primitives like int and string, *every* type that
104+
// we might see in the object graph must be listed here.
105+
CustomTypeResolver resolver = new CustomTypeResolver(
106+
typeof(Person),
107+
typeof(Adult),
108+
typeof(Address));
109+
110+
// Instantiate the serializer.
111+
JavaScriptSerializer serializer = new JavaScriptSerializer(resolver);
112+
113+
// Serialize the object array, then return it.
114+
string serialized = serializer.Serialize(people);
115+
return serialized;
116+
}
117+
118+
static Person[] DeserializePeopleArray(string serializedData)
119+
{
120+
// The custom type resolver to use.
121+
// Note: This is the same list that was provided to the Serialize routine.
122+
CustomTypeResolver resolver = new CustomTypeResolver(
123+
typeof(Person),
124+
typeof(Adult),
125+
typeof(Address));
126+
127+
// Instantiate the serializer.
128+
JavaScriptSerializer serializer = new JavaScriptSerializer(resolver);
129+
130+
// Deserialize the object array, then return it.
131+
Person[] deserialized = serializer.Deserialize<Person[]>(serializedData);
132+
return deserialized;
133+
}
134+
}
135+
136+
public class Person
137+
{
138+
public string Name { get; set; }
139+
public int Age { get; set; }
140+
public Address HomeAddress { get; set; }
141+
}
142+
143+
public class Adult : Person
144+
{
145+
public string Occupation { get; set; }
146+
}
147+
148+
public class Address
149+
{
150+
public string Street1 { get; set; }
151+
public string Street2 { get; set; }
152+
public string City { get; set; }
153+
public string StateOrProvince { get; set; }
154+
public string Country { get; set; }
155+
public string PostalCode { get; set; }
156+
}
157+
158+
// A custom JavaScriptTypeResolver that restricts the payload
159+
// to a set of known good types.
160+
class CustomTypeResolver : JavaScriptTypeResolver
161+
{
162+
private readonly Type[] _allowedTypes;
163+
164+
public CustomTypeResolver(params Type[] allowedTypes)
165+
{
166+
if (allowedTypes == null)
167+
{
168+
throw new ArgumentNullException("allowedTypes");
169+
}
170+
171+
// Make a copy of the array the caller gave us.
172+
_allowedTypes = (Type[])allowedTypes.Clone();
173+
}
174+
175+
public override Type ResolveType(string id)
176+
{
177+
// Iterate over all of the allowed types, looking for a match
178+
// for the 'id' parameter. Calling Type.GetType(id) is dangerous,
179+
// so we instead perform a match on the Type.FullName property.
180+
foreach (Type allowedType in _allowedTypes)
181+
{
182+
if (allowedType.FullName == id)
183+
{
184+
return allowedType;
185+
}
186+
}
187+
188+
// The caller provided a type we don't recognize. This could be
189+
// dangerous, so we'll fail the operation immediately.
190+
throw new ArgumentException("Unknown type: " + id, "id");
191+
}
192+
193+
public override string ResolveTypeId(Type type)
194+
{
195+
// Before we serialize data, quickly double-check to make
196+
// sure we're allowed to deserialize the data. Otherwise it's
197+
// no good serializing something if we can't deserialize it.
198+
if (_allowedTypes.Contains(type))
199+
{
200+
return type.FullName;
201+
}
202+
203+
throw new InvalidOperationException("Cannot serialize an object of type " + type + ". Did you forget to add it to the allow list?");
204+
}
205+
}
206+
}
207+
```
208+
209+
The preceding app outputs the following to the console, formatted for readability.
210+
211+
```txt
212+
Serialized:
213+
[
214+
{
215+
"__type": "SampleApp.Person",
216+
"Name": "Kristen Solstad",
217+
"Age": 15,
218+
"HomeAddress": {
219+
"__type": "SampleApp.Address",
220+
"Street1": "123 Palm Ave",
221+
"Street2": null,
222+
"City": "Some City",
223+
"StateOrProvince": "ST",
224+
"Country": "United States",
225+
"PostalCode": "00000"
226+
}
227+
},
228+
{
229+
"__type": "SampleApp.Adult",
230+
"Occupation": "Mechanic",
231+
"Name": "Alex Johnson",
232+
"Age": 39,
233+
"HomeAddress": {
234+
"__type": "SampleApp.Address",
235+
"Street1": "445 Lorry Way",
236+
"Street2": "Unit 3A",
237+
"City": "Some City",
238+
"StateOrProvince": null,
239+
"Country": "United Kingdom",
240+
"PostalCode": "AA0 A00"
241+
}
242+
}
243+
]
244+
245+
Deserialized 2 people.
246+
Kristen Solstad (Age 15) [SampleApp.Person]
247+
Alex Johnson (Age 39) [SampleApp.Adult]
248+
```
249+
250+
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.
251+
252+
This sample is safe because it uses an `allow-list` mechanism to control deserialization. The code:
253+
254+
* Initializes the `CustomTypeResolver` with an explicit list of allowed types.
255+
* 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.
256+
257+
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:
258+
259+
* Serializing a `Person` or `Adult` also serializes an `Address` as part of the object graph.
260+
* 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.
261+
262+
> [!WARNING]
263+
> 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 and compare their `Type.FullName` property against the incoming `id`, as shown in the preceding sample.
42264
43265
]]></format>
44266
</remarks>
@@ -99,7 +321,7 @@
99321
100322
## Remarks
101323
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.
102-
324+
103325
]]></format>
104326
</remarks>
105327
<block subset="none" type="overrides">

xml/System.Web.Script.Serialization/SimpleTypeResolver.xml

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,26 @@
1515
</Base>
1616
<Interfaces />
1717
<Docs>
18-
<summary>Provides a type resolver for managed types.</summary>
18+
<summary>Provides an insecure type resolver for managed types.</summary>
1919
<remarks>
2020
<format type="text/markdown"><![CDATA[
2121
2222
## Remarks
23-
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.
24-
25-
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.
26-
27-
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.
28-
29-
30-
23+
24+
> [!WARNING]
25+
> 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.
26+
27+
For a sample that demonstrates using a custom `JavaScriptTypeResolver` safely, see <xref:System.Web.Script.Serialization.JavaScriptTypeResolver>.
28+
29+
.NET provides source analyzers that alert you to usage of the dangerous `SimpleTypeResolver` type. For more information about source analyzers, see [Overview of source code analyzers](/visualstudio/code-quality/roslyn-analyzers-overview). For instructions on installing the source analyzers, see [Install .NET Compiler Platform code analyzers](/visualstudio/code-quality/install-roslyn-analyzers).
30+
31+
When the source analyzers package is activated in a project, references to `SimpleTypeResolver` produce one of the following compiler warnings:
32+
33+
- [CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver](/visualstudio/code-quality/ca2321)
34+
- [CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing](/visualstudio/code-quality/ca2322)
35+
3136
## Examples
32-
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.
33-
34-
[!code-aspx-csharp[System.Web.Script.Serialization.TypeResolver#1](~/samples/snippets/csharp/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/CS/Default.aspx#1)]
35-
[!code-aspx-vb[System.Web.Script.Serialization.TypeResolver#1](~/samples/snippets/visualbasic/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/VB/Default.aspx#1)]
36-
37+
3738
]]></format>
3839
</remarks>
3940
<altmember cref="P:System.Type.AssemblyQualifiedName" />

0 commit comments

Comments
 (0)