Microsoft Exchange PowerShell远程反序列化导致RCE(CVE-2023-21707)
引言
在分析CVE-2022-41082(又称ProxyNotShell)时,我们发现了此漏洞。为了全面理解,我们强烈建议阅读ZDI团队的详细分析。
以下是CVE-2022-41082的视觉表示:
ProxyNotShell的sink:
|
|
在[2]处,如果targetTypeForDeserialization != null,它将调用LanguagePrimitives.ConvertTo()将原始obj转换为targetTypeForDeserialization指定的类型。
LanguagePrimitives.ConvertTo()方法在论文《Friday the 13th JSON Attacks》的PSObject gadget部分被引用。论文还讨论了几种利用此方法的方法:
- 调用带1个参数的构造函数
- 调用setter
- 调用静态方法"Parse(string)" [方法3]
- 调用自定义转换 [方法4]
- …
CVE-2022-41082漏洞涉及使用LanguagePrimitives.ConvertTo()两次,采用不同方法。
第一次使用利用[方法4]重建XamlReader类型。为此,使用自定义转换方法Microsoft.Exchange.Data.SerializationTypeConverter.ConvertFrom() -> DeserializeObject(),该方法使用带有白名单的BinaryFormatter反序列化数据。如果反序列化类型恰好是System.Type,目标类型将是System.UnitySerializationHolder,它也在白名单上。
第二次,该过程使用[方法3]启动对静态方法XamlReader.Parse(string)的调用,随后触发远程代码执行(RCE)漏洞。请注意,XamlReader是从步骤1获取的反序列化类型。
CVE-2022-41082的最新补丁引入了新的UnitySerializationHolderSurrogateSelector,它在反序列化System.UnitySerializationHolder过程中验证目标类型。因此,不再可能利用此漏洞调用Type.Parse(string)。此修复有效降低了恶意行为者利用漏洞执行任意代码的风险。
新变种
深入研究LanguagePrimitives.ConvertTo()的[方法3],Exchange实现了自定义PowerShell类型转换:SerializationTypeConverter,方法SerializationTypeConverter.ConvertFrom()将直接调用DeserializeObject [3]:
|
|
在DeserializeObject中,方法CanConvert()将从原始PSObject获取SerializationData属性作为字节数组,如[6]所示,然后直接传递到SerializationTypeConverter.Deserialize() -> BinaryFormatter.Deserialize(),如[7]所示。
在ProxyNotShell的Payload中,SerializationData表示如下:
|
|
通过使用白名单SerializationTypeConverter.allowedTypes(包含约1200个允许类型),可以防止反序列化到远程代码执行(RCE)。
仔细检查此白名单后,发现了41082的新变种,命名为CVE-2023-21707。白名单中的一个允许类型是Microsoft.Exchange.Security.Authentication.GenericSidIdentity。通过利用此白名单并包含特定允许类型,可以显著降低反序列化到RCE的风险。
GenericSidIdentity的继承树:
|
|
如果您有.NET反序列化的经验,您会很快识别出ClaimsIdentity类。此类包含在著名工具ysoserial.net的gadget链中。
Microsoft.Exchange.Security.Authentication.GenericSidIdentity是ClaimsIdentity的子类。在反序列化过程中,首先重建ClaimsIdentity对象,然后调用ClaimsIdentity.OnDeserializedMethod()。
这提供了利用机会,因为我们可以滥用此行为在第二次反序列化阶段触发RCE。
Payload传递
尽管底层bug仍然存在,但ProxyNotShell补丁的实现已有效中和了先前在autodiscover入口点存在的SSRF漏洞。因此,先前的Payload发送方法不再可行。
经过几天的调查,我发现仍然可以远程访问/powershell入口点,但限制是仅限HTTP协议访问:
要以编程方式实现,我们可以使用WSManConnectionInfo和RunspaceFactory.CreateRunspace()建立到目标Exchange服务器的PowerShell会话:
|
|
之后,我们可以使用创建的runspace创建PowerShell会话并调用命令。要传递Payload,我们可以将其作为参数传递,如下所示:
|
|
一个重要方面是PowerShell.AddArgument(object)函数可以接受任何对象作为参数。
此步骤类似于在ProxyNotShell中制作Payload的过程,但我们以编程方式进行。通过使用此函数,我们可以动态地向PowerShell命令添加参数,这为我们的方法提供了更大的灵活性和定制性。
Payload类内容:
|
|
为确保正常功能,此类必须继承System.Exception类型,如本文详细解释。此外,类中必须包含名为SerializationData的公共属性,它将作为绕过gadgetchain GenericSidIdentity的容器。
为此,我们生成一个GenericSidIdentity对象,并将其m_serializedClaims字段值设置为实际的RCE gadgetchain,可以通过ysoserial创建。
虽然有多种方法可以实现此目的,但在我的概念证明中,我选择创建一个继承自GenericIdentity的新类:
并使用自定义序列化绑定器在序列化期间重写类名:
为了成功执行漏洞利用,需要满足某些先决条件:
- 攻击者的机器应能够访问目标Exchange服务器的端口80。
- PowerShell入口点的身份验证方法必须是Kerberos(而不是NTLM),需要在运行漏洞利用时访问域控制器的端口88。
请注意,由于技术限制,此漏洞利用对于面向互联网的Exchange服务器不可行。
以下图像详细说明了成功的代码利用,包括执行证明和有关结果调用堆栈的信息。
感谢阅读!
演示
参考文献
- https://www.zerodayinitiative.com/blog/2022/11/14/control-your-types-or-get-pwned-remote-code-execution-in-exchange-powershell-backend
- https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf
- https://learn.microsoft.com/en-us/powershell/exchange/connect-to-exchange-servers-using-remote-powershell?view=exchange-ps
- https://www.zerodayinitiative.com/blog/2022/11/14/control-your-types-or-get-pwned-remote-code-execution-in-exchange-powershell-backend