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的有效载荷中,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。
有效载荷传递
尽管底层bug仍然存在,但ProxyNotShell补丁的实现有效中和了先前在autodiscover入口点存在的SSRF漏洞。因此,先前的有效载荷发送方法不再可行。
经过几天的调查,我发现仍然可以远程访问/powershell入口点,尽管限制仅允许HTTP协议访问:
来源:https://learn.microsoft.com/en-us/powershell/exchange/connect-to-exchange-servers-using-remote-powershell?view=exchange-ps
以编程方式实现,我们可以使用WSManConnectionInfo和RunspaceFactory.CreateRunspace()建立到目标Exchange服务器的powershell会话:
|
|
之后,我们可以使用创建的runspace创建PowerShell会话并调用命令。为了传递有效载荷,我们可以将其作为参数传递,如下所示:
|
|
一个重要方面是,PowerShell.AddArgument(object)函数可以接受任何对象作为参数。
此步骤类似于在ProxyNotShell中制作有效载荷的过程,但不是手动制作,而是以编程方式进行。通过使用此函数,我们可以动态添加参数到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