https://github.com/Y4er/dotnet-deserialization/blob/main/DataContractJsonSerializer.md

DataContractJsonSerializer

在dotnet中对于对象转json的处理有几大库,DataContractJsonSerializer、Json.net、JavaScriptSerializer。其中DataContractJsonSerializer、JavaScriptSerializer是dotnet自带的标准库,本文讲解DataContractJsonSerializer的使用。

这个类和xmlSerializer、DataContractSerializer一样 都是需要控制Type值

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

namespace DataContractJsonDeserializer
{
[DataContract]
class Person
{
[DataMember]
internal string name;

[DataMember]
internal int age;
}
class Program
{
static void Main(string[] args)
{
var p = new Person();
p.name = "John";
p.age = 42;
var memoryStream = new MemoryStream();
var ser = new DataContractJsonSerializer(typeof(Person));
ser.WriteObject(memoryStream, p);
memoryStream.Position = 0;
var sr = new StreamReader(memoryStream);
Console.WriteLine(sr.ReadToEnd());
memoryStream.Position = 0;
Person p1 = (Person)ser.ReadObject(memoryStream);
Console.WriteLine(p1.name);
Console.ReadKey();
}
}
}

Person需要标记DataContract特性,序列化成员需要标记DataMember特性。通过WriteObject和ReadObject进行序列化和反序列化,demo和DataContractSerializer大致相同。注意构造函数传入了Person的type类型。如果实际环境type参数可控,那么可以造成RCE。

WindowsPrincipal攻击链

image-20240313102416114

yso生成的payload 关键点在于这个__type __type字段可以通过DataContractJsonSerializerSettings传入DataContractJsonSerializer的构造方法

image-20240313102552811

写个demo来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Principal;
using System.Text.RegularExpressions;

namespace DataContractJsonDeserializer
{
[DataContract]
internal class Person
{
[DataMember]
internal string name;
[DataMember]
internal int age;
[DataMember]
object o;
}
class Program
{
static void Main(string[] args)
{
var settings = new DataContractJsonSerializerSettings();
settings.EmitTypeInformation = EmitTypeInformation.Always;
var ser = new DataContractJsonSerializer(typeof(Person), settings);
using (FileStream file = new FileStream("1.json", FileMode.OpenOrCreate))
{
Person person = new Person();
person.name = "jack";
person.age = 19;
ser.WriteObject(file, person);
}
Console.ReadKey();
}
}
}

当传入settings时 json中会包含__type

image-20240313102711243

当不传入settings时 json中不包含了__type

image-20240313102832179

当setting的EmitTypeInformation设置为EmitTypeInformation.Always时json会包含type信息。即上文yso生成的__type字段。

Type可控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

namespace DataContractJsonDeserializer
{
class Program
{
static void Main(string[] args)
{
var settings = new DataContractJsonSerializerSettings();
settings.EmitTypeInformation = EmitTypeInformation.Always;
var ser = new DataContractJsonSerializer(Type.GetType("System.Security.Principal.WindowsPrincipal"), settings);

using (FileStream file = new FileStream("1.json", FileMode.OpenOrCreate))
{
ser.ReadObject(file);
}
Console.ReadKey();
}
}
}

当我们的type可控的时候 我们使用yso生成的payload是可以直接打的

image-20240313103446937

在我们使用yso生成的json传入进去后 这个setting可有可无的 实战中只需要控制这个type值就行了

KnownType特性

这个东西 Y4er师傅说了难利用 看代码也能看出来 他是直接在代码中设置

1
[KnownType(typeof(WindowsPrincipal))]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Principal;

namespace DataContractJsonDeserializer
{
[DataContract]
[KnownType(typeof(WindowsPrincipal))]
internal class Person
{
[DataMember]
internal string name;

[DataMember]
internal int age;
[DataMember]
object o;
}
class Program
{
static void Main(string[] args)
{
var ser = new DataContractJsonSerializer(typeof(Person));
using (FileStream file = new FileStream("1.json", FileMode.OpenOrCreate))
{
ser.ReadObject(file);
}
Console.ReadKey();
}
}
}

构造函数中传入KnownType

这个相比于第二种的话 是更好利用的 因为这个是可以通过构造函数来进行利用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Principal;

namespace DataContractJsonDeserializer
{
[DataContract]
internal class Person
{
[DataMember]
internal string name;
[DataMember]
internal int age;
[DataMember]
object o;
}
class Program
{
static void Main(string[] args)
{
var settings = new DataContractJsonSerializerSettings();
settings.EmitTypeInformation = EmitTypeInformation.Always;
var ser = new DataContractJsonSerializer(typeof(Person), new List<Type> { typeof(WindowsPrincipal) });
using (FileStream file = new FileStream("1.json", FileMode.OpenOrCreate))
{
ser.ReadObject(file);
}
Console.ReadKey();
}
}
}

image-20240313104043879

image-20240313104032117

控制这个KnownType的话 就是Type可以是任意类了 必须是标记[DataContract]特性的类

以上的三种方法在用yso生成的payload的时候都能利用

除了上文三种方法外,再引入一个新的东西IDataContractSurrogate 接口

IDataContractSurrogate

image-20240313105413243

参数解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// 摘要:
// 初始化 System.Runtime.Serialization.Json.DataContractJsonSerializer 类的新实例,以便序列化或反序列化指定类型的对象。
// 此方法还指定了可在对象图中呈现的已知类型的列表、要序列化或反序列化的最大图项数、是忽略意外数据还是发出类型信息以及自定义序列化的代理项。
//
// 参数:
// type:
// 序列化或反序列化的实例的类型。
//
// knownTypes:
// 一个包含内容的根元素名称的 System.Xml.XmlDictionaryString。
//
// maxItemsInObjectGraph:
// System.Collections.Generic.IEnumerable`1 的一个 System.Type,其中包含可在对象图中呈现的类型。
//
// ignoreExtensionDataObject:
// 若要在序列化时忽略 true 接口并在反序列化时忽略意外数据,则为 System.Runtime.Serialization.IExtensibleDataObject;否则为
// false。 默认值为 false。
//
// dataContractSurrogate:
// 一个用于自定义序列化过程的 System.Runtime.Serialization.IDataContractSurrogate 实现。
//
// alwaysEmitTypeInformation:
// 若要发出类型信息,则为 true;否则为 false。 默认值为 false。
public DataContractJsonSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, IDataContractSurrogate dataContractSurrogate, bool alwaysEmitTypeInformation);

其中提到了dataContractSurrogate参数,用于自定义序列化过程的 System.Runtime.Serialization.IDataContractSurrogate 实现。

因为DataContractJsonSerializer只有已知类型knownTypes的对象才能被序列化,而在实体类中不可避免的需要接入其他没有被标记DataContract特性的类,而没标记DataContract特性,就不在konwnTypes中,就不能被序列化。所以引入IDataContractSurrogate接口,作用是控制实体类引入了不在knownTypes中的类型实例应该如何被序列化存储。

这里就和上文的那个WindowsPrincipal类的KnownType对应了

所以要是就是Type值设置为需要反序列化的类 要么就是KnowType设置 这两个其中一个设置就行

(如果两者都不设置的话 就选下面的这个IDataContractSurrogate接口)

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Xml.Serialization;

namespace DataContractJsonDeserializer
{
[DataContract]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public Dog dog;
}
public class Dog
{
public string Name { get; set; }
}

[DataContract]
class DogSurrogated
{
[DataMember()]
public string xmlData;
}
class DogSurrogate : IDataContractSurrogate
{
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
Console.WriteLine("GetCustomDataToExport invoked");
return null;
}

public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
Console.WriteLine("GetCustomDataToExport invoked");
return null;
}

public Type GetDataContractType(Type type)
{
Console.WriteLine($"GetDataContractType invoked, {type}");
if (type.IsAssignableFrom(typeof(Dog)))
{
return typeof(DogSurrogated);
}
return type;
}

public object GetDeserializedObject(object obj, Type targetType)
{
Console.WriteLine($"GetDeserializedObject invoked {obj}");
if (obj is DogSurrogated)
{
DogSurrogated ps = (DogSurrogated)obj;
XmlSerializer xs = new XmlSerializer(typeof(Dog));
return (Dog)xs.Deserialize(new StringReader(ps.xmlData));
}
return obj;
}

public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
Console.WriteLine($"GetKnownCustomDataTypes invoked. {customDataTypes}");

}

public object GetObjectToSerialize(object obj, Type targetType)
{
Console.WriteLine($"GetObjectToSerialize invoked,{obj},{targetType.FullName}");
if (obj is Dog)
{
DogSurrogated ps = new DogSurrogated();
XmlSerializer xs = new XmlSerializer(typeof(Dog));
StringWriter sw = new StringWriter();
xs.Serialize(sw, (Dog)obj);
ps.xmlData = sw.ToString();
return ps;
}
return obj;
}

public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
Console.WriteLine("GetReferencedTypeOnImport invoked");
Console.WriteLine("\t Type Name: {0}", typeName);

if (typeName.Equals("DogSurrogated"))
{
Console.WriteLine("Returning Dog");
return typeof(Dog);
}
return null;
}

public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
{
Console.WriteLine("ProcessImportedType invoked");
return typeDeclaration;
}
}
public class Program
{
public static void Main(string[] vs)
{
Person person = new Person();
person.Name = "jack";
Dog dog = new Dog();
dog.Name = "jjjj";
person.dog = dog;
List<Type> knownTypes = new List<Type>();
DogSurrogate surrogate = new DogSurrogate();
//DataContractSerializer surrogateSerializer = new DataContractSerializer(typeof(Person), knownTypes, Int16.MaxValue, false, true, surrogate);
DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(Person), knownTypes, int.MaxValue, false, surrogate, false);
FileStream fs = new FileStream("1.txt", FileMode.OpenOrCreate);
dataContractJsonSerializer.WriteObject(fs, person);
fs.Close();
Console.WriteLine(File.ReadAllText("1.txt"));

Person p1 = (Person)dataContractJsonSerializer.ReadObject(File.Open("1.txt", FileMode.Open));
Console.WriteLine($"person.Name:{p1.Name}\t person.dog.Name:{p1.dog.Name}");
Console.ReadKey();
}
}
}

image-20240313111309274

这里的话一共是有三个类

其中Person标记了DataContract,但是其dog字段的类型Dog没有标记DataContract,所以新建了一个DogSurrogated类来表示Dog类型。

在代码中新建了DogSurrogate类实现IDataContractSurrogate接口方法,并将其传入DataContractJsonSerializer构造函数。

DogSurrogate在这个类中 实现了序列化和反序列化操作

image-20240313111639103

image-20240313111645855

这里这个接口也是一样 我们可以自己定义需要反序列化的类 在实战中 我们只需要重写这个接口 在里面写上对应的类就行了

其实对这个IDataContractSurrogate 接口来讲 我认为实战中是有点难进行利用的 应该遇到会很少