原文:https://www.zerodayinitiative.com/blog/2024/9/4/exploiting-exchange-powershell-after-proxynotshell-part-1-multivaluedproperty
exp:https://github.com/Chocapikk/CVE-2024-8504
如果访问不了exp请访问:https://pan.quark.cn/s/c3e7aa865ee5
介绍
您可能已经熟悉 Exchange ProxyNotShell 链、CVE-2022-41040 和 CVE-2022-41082。它允许任何经过身份验证的 Exchange 用户实现远程代码执行。在 Microsoft 发布补丁之前,ProxyNotShell 已被广泛利用。
我在这篇博文中描述了 ProxyNotShell 攻击链,特别是它的 RCE 向量。在继续阅读本文之前,请确保您熟悉原始问题,因为本文将重点介绍如何绕过补丁。
在这篇博文中,我想从 2 个 RCE 漏洞开始:
• ZDI-23-163/ CVE-2023-21529 – 滥用允许的MultiValuedProperty
类别。• ZDI-23-881/ CVE-2023-32031 – 绕过 CVE-2023-21529,滥用未被阻止的Command
类别。
无需担心 ProxyShell 路径混淆即可访问 PowerShell
最初的路径混淆漏洞 CVE-2021-34473 是由 Orange Tsai 发现的。他将其与 CVE-2021-34523 和 CVE-2021-31207 一起使用,实现了预授权远程代码执行,形成了被称为 ProxyShell 的攻击链。
微软最初针对路径混淆的补丁并没有消除问题的根源,而是将其置于身份验证之后。补丁发布后,该漏洞被利用上述 ProxyNotShell 链在身份验证后执行远程代码。
利用路径混淆,威胁行为者可以通过向autodiscover
端点发送 HTTP 请求来到达 Exchange PowerShell 后端。
在 ProxyNotShell 补丁发布后,似乎这种攻击媒介已被完全阻止,尽管我必须承认我从未完全验证过该补丁。尽管如此,低权限攻击者仍然可以直接访问 Exchange PowerShell Remoting,但需经过 Kerberos 身份验证。这是因为每个 Exchange 用户都可以触发一些 Exchange PowerShell cmdlet,例如。可以在此处Get-Mailbox
找到描述与 Exchange PowerShell 直接交互的说明。
由于需要 Kerberos 身份验证,因此此攻击面可能仅限于内部攻击者,也就是说,已经存在于组织网络中的攻击者。不过,仍有很多值得担忧的理由。如果任何域帐户(和组织成员)可以升级到 Exchange 服务器上的 SYSTEM,那就不好了。
ProxyNotShell CVE-2022-41082 RCE 补丁
CVE-2022-41082 是 ProxyNotShell 链的 RCE 部分,已通过引入 类进行修复Microsoft.Exchange.Diagnostics.UnitySerializationHolderSurrogateSelector
。它进行了扩展SurrogateSelector
,其主要目标是验证在 反序列化期间检索到的类型UnitySerializationHolder
。它通过对照允许列表检查类型来实现这一点。
微软的做法似乎很合适。允许列表可能是对抗反序列化问题和类似的基于类型的漏洞的最佳方法。但是,当允许列表很广泛时,可能会在其中找到一些可用于利用的类型。我决定走这条路,寻找潜在危险的允许类。
ZDI-23-162/ CVE-2023-21529 – 允许 MultiValuedProperty 导致 RCE
Exchange 允许列表可分为两个主要部分:• 允许的常规类型列表。• 允许的通用类型列表。
泛型类型似乎特别有趣,因为它们允许包含任意内部类型。此外,还可以通过反序列化检索泛型类型UnitySerializationHolder
。让我们回顾一下成员中定义的允许泛型列表Microsoft.Exchange.Data.SerializationTypeConverter.allowedGenerics
。
private static readonly string[] allowedGenerics = new string[]
{
"Microsoft.Exchange.Collections.ReadOnlyCollection`1",
"Microsoft.Exchange.Data.DagNetMultiValuedProperty`1",
"Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1",
"Microsoft.Exchange.Data.Directory.ConfigurationXMLHelper+ConfigXMLCache`1",
"Microsoft.Exchange.Data.MultiValuedProperty`1",
"Microsoft.Exchange.Data.QueueViewer.QueueViewerPropertyDefinition`1",
"Microsoft.Exchange.Data.StateDurationData`1",
"Microsoft.Exchange.Data.Unlimited`1",
"Microsoft.Exchange.MailboxReplicationService.Report+ListWithToString`1",
"System.Collections.Generic.Dictionary`2",
...
};
列表的第一部分尤其有趣,因为它包含自定义 Exchange 类型。事实证明,涉及检索Microsoft.Exchange.Data.MultiValuedProperty<T>
或Microsoft.Exchange.Data.DagNetMultiValuedProperty<T>
通用类的反序列化可能导致远程代码执行。
大家可能还记得,PowerShell Remoting 反序列化允许调用任何允许类型的单参数构造函数(只要该参数也可以反序列化)。这让我们考虑一个单参数构造函数MultiValuedProperty<T>
。
public MultiValuedProperty(object value) : this(true, true, null, MultiValuedProperty<T>.GetObjectAsEnumerable(value), null, null, false)
{
}
如您所见,它接受类型为的参数object
。因此,攻击者可以提供任何允许的 PowerShell Remoting 可反序列化类的实例。此构造函数调用另一个接受更多参数的构造函数。
构造函数调用后会发生大量处理。主要关注的是最终到达方法ValueConvertor.ConvertValue
。这里,攻击者控制的类型作为第二个参数提供,而攻击者控制的对象作为第一个参数提供。这是提供给构造函数的对象MultiValuedProperty
。
public static object ConvertValue(object originalValue, Type resultType, IFormatProvider formatProvider)
{
if (null == resultType)
{
throw new ArgumentNullException("resultType");
}
if (originalValue == null)
{
if (resultType.GetTypeInfo().IsValueType && (!resultType.GetTypeInfo().IsGenericType || !resultType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))))
{
throw new InvalidOperationException(DataStrings.ErrorCannotConvertNull(resultType.ToString()));
}
return null;
}
else
{
Type type = originalValue.GetType();
object result = null;
...
// Removed for readability
...
if (ValueConvertor.TryParseConversion(originalValue, type, resultType, formatProvider, out result)) // [1]
{
return result;
}
if (ValueConvertor.TryConstructorConversion(originalValue, type, resultType, formatProvider, out result)) // [2]
{
return result;
}
...
// Removed for readability
..
throw new NotImplementedException(DataStrings.ErrorOperationNotSupported(type.ToString(), resultType.ToString()));
}
}
在 时[1]
,它调用ValueConvertor.TryParseConversion
。此调用看起来特别有趣,因为方法名称表明该Parse
方法涉及其中。
在[2]
,它呼叫TryConstructorConversion
。
现在让我们关注基于解析的转换。
private static bool TryParseConversion(object originalValue, Type originalType, Type resultType, IFormatProvider formatProvider, out object result)
{
if (originalValue is string) // [1]
{
try
{
try
{
result = ValueConvertor.ConvertValueFromString((string)originalValue, resultType, formatProvider); // [2]
return true;
}
catch (NotImplementedException) // [3]
{
IEnumerable<MethodInfo> source = from methodinfo in resultType.GetTypeInfo().GetDeclaredMethods("Parse")
where methodinfo.IsPublic && methodinfo.IsStatic && methodinfo.GetParameters().Length == 1
select methodinfo; // [4]
if (source.Any<MethodInfo>())
{
result = source.First<MethodInfo>().Invoke(null, new object[]
{
originalValue
}); // [5]
return true;
}
}
}
..
// Removed for readability
..
}
在此阶段,值得注意的是具体参数的值:
• originalValue
- 攻击者提供给MultiValuedProperty
构造函数的值。• originalType
- 的类型originalValue
。• resultType
- 攻击者指定的泛型类型的类型参数(“T”)MultiValuedProperty<T>
。
在[1]
,该方法检查是否originalType
是类型string
在 时[2]
,它调用ConvertValueFromString
。此方法也在反序列化过程中被调用。此方法对几种可能的转换进行硬编码,如果未实现从到 的NotImplementedException
转换,则抛出异常。originalType``resultType
在 时[3]
,它捕获了异常。
在,它从攻击者控制的 中[4]
检索公共静态方法。Parse``resultType
在 时,它使用攻击者指定的值[5]
调用该方法。Parse
总而言之,MultiValuedProperty<T>
泛型类实现了另一种调用Parse
方法的方式。这可能导致XamlReader.Parse(String)
使用攻击者控制的字符串调用该方法。此外,还TryConstructorConversion
允许调用给定类的单参数构造函数。
此时,我们可以看到该类MultiValuedProperty<T>
实现了 PowerShell Remoting 的两个最强大的转换。由于它是一种内部反序列化机制,因此它被列入允许列表中。攻击者可以滥用它,例如调用任何可访问类的单参数构造函数。这成为我后续漏洞研究的基本构建块。
作为如何被滥用的一个例子MultiValuedProperty<T>
,请考虑以下代码:
MultiValuedProperty<XamlReader> obj = new MultiValuedProperty<XamlReader>("XAML GADGET HERE");
此行模拟了我们在利用过程中通过 Exchange PowerShell Remoting 实现的操作:
• 攻击者提供一个UnitySerializationHolder
指定允许MultiValuedProperty<T>
类型的序列化对象。类型参数T
设置为System.Windows.Markup.XamlReader
。• 对我们的类型执行允许列表检查:MultiValuedProperty<XamlReader>
。检查成功,因为: (1)MultiValuedProperty<T>
存在于允许列表中,并且 (2) 类型参数中指定的类型XamlReader
根本不经过验证。•构造函数通过调用静态方法来MultiValuedProperty
实例化对象。• 由于攻击者控制输入字符串,他们可以提供任何 XAML 反序列化小工具来实现远程代码执行。XamlReader``XamlReader.Parse(String)
简化的攻击方案如下图所示。
正如我们所展示的,允许列表并不总是安全的,需要仔细检查。即使在像 Microsoft Exchange 这样成熟的产品中,允许的类也可能包含可能被滥用的功能。对于允许列表中包含的泛型类,情况尤其如此。泛型(内部)类型应始终由类型控制机制验证。否则,您的允许类可能会被滥用。此外,还应验证类继承。例如,假设 MicrosoftMultiValuedProperty<T>
从允许列表中删除。我们仍然可以通过允许的类型访问它DagNetMultiValuedProperty<T>
:
[Serializable]
public class DagNetMultiValuedProperty<T> : MultiValuedProperty<T>
{
public DagNetMultiValuedProperty(ICollection values) : base(values)
{
}
public DagNetMultiValuedProperty(object value) : base(value)
{
}
...
// Removed for readability
...
}
DagNetMultiValuedProperty<T>
继承自MultiValuedProperty<T>
。其单参数构造函数调用基类的构造函数。因此,这是触发危险例程的另一种方式,即使MultiValuedProperty<T>
从允许列表中删除,也可能会被滥用。
ZDI-23-881/ CVE-2023-32031 – 使用命令类绕过内部拒绝列表
在 CVE-2023-21529 中,我滥用了可通过允许列表类访问的内部反序列化类机制MultiValuedProperty<T>
。在考虑可能的修补程序时,有两种方法可供选择:
MultiValuedProperty<T>
从允许列表中 删除。在内部反序列化机制中实现额外的类型控制
MultiValuedProperty
。
MultiValuedProperty
Exchange 经常使用它,因此无法将其从允许列表中删除。ValueConvertor.ConvertValue
不过,在定义的内部反序列化机制中实现类型控制看起来是个不错的选择。补丁如下所示:
public static object ConvertValue(object originalValue, Type resultType, IFormatProvider formatProvider)
{
ChainedSerializationBinder.ValidateResultType(resultType); // [1]
if (null == resultType)
{
throw new ArgumentNullException("resultType");
}
...
您可以看到ChainedSerializationBinder.ValidateResultType
引入了该方法,以限制攻击者可以指定的类型。
因此,如果攻击者提供类型MultiValuedProperty<XamlReader>
,则会引发异常,因为类型XamlReader
未通过新验证。不过,深入研究验证机制后,我发现此处的类型验证基于拒绝列表。这里没有实现可与一起使用的类型的允许列表,而是MultiValuedProperty
使用了拒绝列表。如果您看过我的Hexacon 谈论 .NET 反序列化,您可能知道我喜欢摆弄拒绝列表。
Exchange 拒绝列表实际上相当不错,它包含数十个类。但是,它几乎不包含任何内部 Exchange 类。我的想法是寻找一个类,该类:
• 不在拒绝列表中。 • 实现导致可利用漏洞的公共静态方法,或• 实现接受单个参数并导致可利用漏洞的公共构造函数。Parse(String)
MultiValuedProperty
当与内部反序列化链接时,此类可能会被滥用。
基于构造函数的反序列化由前面提到的方法处理TryConstructorConversion
,它与 PowerShell Remoting 中实现的方法非常相似。
我很快就找到了这个Microsoft.Diagnostics.Runtime.Utilities.Command
课程:
public Command(string commandLine) : this(commandLine, new CommandOptions()) // [1]
{
}
public Command(string commandLine, CommandOptions options)
{
this._options = options;
this._commandLine = commandLine;
Match match = Regex.Match(commandLine, "^\\s*\"(.*?)\"\\s*(.*)");
if (!match.Success)
{
match = Regex.Match(commandLine, "\\s*(\\S*)\\s*(.*)");
}
ProcessStartInfo processStartInfo = new ProcessStartInfo(match.Groups[1].Value, match.Groups[2].Value); // [2]
this._process = new Process();
this._process.StartInfo = processStartInfo; // [3]
this._output = new StringBuilder();
...
if (options.outputFile != null)
{
this._outputStream = File.CreateText(options.outputFile);
}
try
{
this._process.Start(); // [4]
}
catch (Exception ex)
{
...
}
}
在 处[1]
,Command(String)
构造函数调用Command(String, CommandOptions)
构造函数。
在 处[2]
,实例化一个新的ProcessStartInfo
,并且从攻击者控制的输入中检索进程名称和参数。
在[3]
,Process.StartInfo
设置为ProcessStartInfo
来自 行的对象[2]
。
在 时[4]
,一个新的进程开始。
此类未包含在拒绝列表中,因此以下代码:
MultiValuedProperty<Command> mvp = new MultiValuedProperty<Command>("cmd.exe /c calc.exe");
导致执行cmd.exe /c calc.exe
。就是这样。总结一下,我做了以下事情:
• 我使用允许列表中的MultiValuedProperty
类来访问内部反序列化机制。此机制受到可滥用类型的拒绝列表的保护。• 我提供了Command
拒绝列表中没有的类。这允许执行任意命令。
概括
在这篇博文中,我描述了 CVE-2023-21529 和 CVE-2023-32031。在这些漏洞中,我滥用了允许列表和拒绝列表类来在 Exchange 上实现 RCE。不过,这并不是我对 Exchange 漏洞研究的结束。在 CVE-2023-32031 补丁之后,我还能够提供另外两个完整的 RCE 链。