Windows漏洞类型:利用IDispatch访问被捕获的COM对象
面向对象的远程技术(如DCOM和.NET Remoting)使得开发跨进程和安全边界的面向对象接口变得非常容易。这是因为它们设计用于支持各种对象,不仅包括服务中实现的对象,还包括任何其他可远程兼容的对象。例如,如果您想跨客户端-服务器边界公开XML文档,可以使用现有的COM或.NET库并将该对象返回给客户端。默认情况下,当对象返回时,它通过引用进行封送,导致对象保留在进程外服务器中。
这种灵活性有许多缺点,其中之一就是本文的主题——被捕获对象漏洞类型。并非所有可远程的对象都必然安全。例如,前面提到的XML库(在COM和.NET中)支持在XSLT文档上下文中执行任意脚本代码。如果XML文档对象可以通过边界访问,那么客户端可以在服务器进程的上下文中执行代码,这可能导致权限提升或远程代码执行。
有几种场景可能引入此漏洞类型。最常见的是不安全对象被无意中共享。例如CVE-2019-0555。这个漏洞的引入是因为在开发Windows运行时库时需要XML文档对象。开发人员决定在现有的XML DOM Document v6 COM对象中添加一些代码,以公开运行时特定接口。由于这些运行时接口不支持XSLT脚本功能,假设跨权限边界公开是安全的。不幸的是,恶意客户端可以查询仍然可访问的旧IXMLDOMDocument接口,并使用它运行XSLT脚本来逃逸沙箱。
另一个场景是存在异步封送原语。这是指对象可以通过值和引用进行封送,平台选择引用作为默认机制。例如,FileInfo和DirectoryInfo .NET类都是可序列化的,因此可以通过值封送到.NET远程服务。但它们也派生自MarshalByRefObject类,这意味着它们可以通过引用进行封送。攻击者可以利用这一点,向服务器发送对象的序列化形式,当反序列化时,将在服务器进程中创建对象的新实例。如果攻击者可以读回创建的对象,运行时将通过引用将其封送回攻击者,使对象被捕获在服务器进程中。最后,攻击者可以调用对象上的方法,例如创建新文件,这些文件将以服务器的权限执行。此攻击在我的ExploitRemotingService工具中实现。
我将提到的最后一个场景与本文最相关,即滥用远程技术用于查找和实例化对象的内置机制来创建意外对象。例如,在COM中,如果您可以找到调用CoCreateInstance API的代码路径,并使用任意CLSID,然后将该对象传递回客户端,那么您可以使用它在服务器上下文中运行任意代码。这种形式的一个例子是CVE-2017-0211,这是一个跨安全边界公开结构化存储对象的漏洞。存储对象支持IPropertyBag接口,可用于在服务器上下文中创建任意COM对象并将其返回给客户端。这可以通过在服务器中创建XML DOM Document对象,通过引用封送回客户端,然后使用XSLT脚本功能在服务器上下文中运行任意代码来提升权限来利用。
IDispatch的作用是什么?
IDispatch接口是OLE Automation功能的一部分,这是COM的原始用例之一。它允许COM客户端与服务器进行后期绑定,以便可以从脚本语言(如VBA和JScript)使用对象。该接口完全支持跨进程和权限边界,尽管它更常用于进程内组件(如ActiveX)。
为了便于在运行时调用COM对象,服务器必须向客户端公开一些类型信息,以便客户端知道如何打包参数以通过接口的Invoke方法发送。类型信息存储在开发人员定义的磁盘上的类型库文件中,客户端可以使用IDispatch接口的GetTypeInfo方法查询库。由于类型库接口的COM实现通过引用进行封送,返回的ITypeInfo接口被捕获在服务器中,任何对其调用的方法都将在服务器的上下文中执行。
ITypeInfo接口公开了两个有趣的方法,客户端可以调用:Invoke和CreateInstance。事实证明,Invoke对于我们的目的不是那么有用,因为它不支持远程调用,只有在类型库加载到当前进程中时才能调用。然而,CreateInstance被实现为可远程的,这将通过调用CoCreateInstance从CLSID实例化COM对象。关键的是,创建的对象将在服务器的进程中,而不是客户端。
但是,如果您查看链接的API文档,没有可以传递给CreateInstance的CLSID参数,那么类型库接口如何知道要创建什么对象?ITypeInfo接口表示可以存在于类型库中的任何类型。GetTypeInfo返回的类型仅包含有关客户端要调用的接口的信息,因此调用CreateInstance将仅返回错误。然而,类型库还可以存储“CoClass”类型的信息。这些类型定义了要创建的对象的CLSID,因此调用CreateInstance将成功。
我们如何从接口类型信息对象转到表示类的对象?ITypeInfo接口为我们提供了GetContainingTypeLib方法,该方法返回对包含的ITypeLib接口的引用。然后可以使用该接口枚举类型库中所有支持的类。如果远程公开,一个或多个类可能不安全。让我们通过使用我的OleView.NET PowerShell模块进行一个工作示例,首先我们想找到一些也支持IDispatch的目标COM服务。这将为我们提供权限提升的潜在途径。
|
|
Get-ComClass的-Service开关返回在本地服务中实现的类。然后我们查询所有支持的接口,我们不需要此命令的输出,因为查询的接口存储在Interfaces属性中。最后,我们选择任何公开IDispatch的COM类,得到5个候选。接下来,我们将选择第一个类WaasRemediation并检查其类型库以寻找有趣的类。
|
|
脚本创建COM对象,然后使用Import-ComTypeLib命令获取类型库接口。我们可以通过使用Get-ComObjRef封送它然后提取进程信息来检查类型库接口是否真的在进程外运行,显示它在共享服务可执行文件svchost.exe的实例中运行。通过接口检查类型库很痛苦,为了更容易显示支持的类,我们可以使用Parse方法将库解析为更易于使用的对象模型。然后我们可以转储有关库的信息,包括其类的列表。
不幸的是,对于此COM对象,类型库支持的唯一类已经注册在服务中运行,因此我们一无所获。我们需要的是一个仅在本地进程中注册但由类型库公开的类。这是一种可能性,因为类型库可以由本地进程内组件和进程外服务共享。
我检查了其他4个COM类(其中一个注册不正确,未由相应服务公开),没有找到有用的类来尝试利用。您可能决定在这一点放弃,但事实证明有一些可访问的类,它们只是隐藏了。这是因为类型库可以引用其他类型库,可以使用相同的接口集进行检查。让我们看看:
|
|
在示例中,我们可以使用ReferencedTypeLibs属性显示解析库时遇到哪些类型库。我们可以看到stdole的单个条目,这基本上总是被导入。如果您幸运,可能还有其他导入的库可以检查。我们可以解析stdole库以检查其类列表。类型库导出了两个类,如果我们检查StdFont的服务器,可以看到它仅指定在进程中创建,我们现在有一个目标类来寻找漏洞。要获取stdole类型库的进程外接口,我们需要找到一个引用它的类型。引用的原因是常见接口(如IUnknown和IDispatch)在库中定义,因此我们需要查询我们可以直接访问的接口的基本类型。让我们尝试在COM服务中创建对象。
|
|
我们通过GetRefTypeOfImplType和GetRefTypeInfo的组合查询现有接口的基本类型,然后使用GetContainingTypeLib获取引用的类型库接口。我们可以解析库以确信我们已获得stdole库。接下来,我们获取StdFont类的类型信息并调用CreateInstance。我们可以检查对象的进程以确保它在进程外创建,结果显示它被捕获在服务进程中。作为最终检查,我们可以查询对象的接口以证明它是字体对象。
现在我们只需要找到一种方法来利用这两个类中的其中一个,第一个问题是只能访问StdFont对象。StdPicture对象进行检查以防止它在进程外使用。我在字体对象中找不到有用的可利用行为,但我没有花太多时间寻找。当然,如果其他人想在该类中寻找合适的漏洞,请自便。
因此,这项研究陷入了死胡同,至少就系统服务而言。可能有一些COM服务器可以从沙箱访问,但对从AppContainer可访问的服务的初步分析没有显示任何明显的候选。然而,在进一步思考之后,我意识到它可能作为一种注入技术有用,注入到以相同权限级别运行的进程中。例如,我们可以劫持StdFont的COM注册,使用TreatAs注册表键指向任何其他类。这个其他类将是可利用的,例如将JScript引擎加载到目标进程并运行脚本。
尽管如此,注入技术不是我通常在此博客上讨论的内容,那更像是恶意软件的领域。然而,有一个场景可能具有有趣的安全影响。如果我们能使用它注入到Windows受保护进程中呢?在一个奇怪的命运转折中,我们刚刚检查的WaaSRemediationAgent类可能正是我们的门票:
|
|
当我们检查托管服务的保护级别时,它配置为在PPL-Windows级别运行!让我们看看是否能从这项研究中挽救一些价值。
受保护进程注入
我之前曾博客(并演示)过关于注入Windows受保护进程的主题。我建议重新阅读那篇博客文章以更好地了解以前的注入攻击。然而,一个关键点是Microsoft不认为PPL是安全边界,因此他们通常不会在安全公告中及时修复任何漏洞,但可能会选择在新版本的Windows中修复它。
想法很简单,我们将重定向StdFont类注册以指向另一个类,以便当我们通过类型库创建它时,它将在受保护进程中运行。选择使用StdFont应该更通用,因为如果WaaSRemediationAgent被删除,我们可以转而使用不同的COM服务器。我们只需要一个合适的类,它可以让我们获得任意代码执行,并且也在受保护进程中工作。
不幸的是,这立即排除了任何脚本引擎(如JScript)。如果您重新阅读我上一篇博客文章,代码完整性模块明确阻止常见脚本引擎在受保护进程中加载。相反,我需要一个可进程外访问并可加载到受保护进程中的类。我意识到一个选项是加载已注册的.NET COM类。我曾博客讨论过.NET DCOM如何可利用,并且不应使用,但在这种情况下,我们想要这种错误性。
博客文章讨论了利用序列化原语,然而有一个更简单的攻击,我通过使用System.Type类 over DCOM利用。通过访问Type对象,您可以执行任意反射并调用任何您喜欢的方法,包括从字节数组加载程序集,这将绕过签名检查并完全控制受保护进程。
Microsoft修复了此行为,但他们留下了一个配置值AllowDCOMReflection,允许您再次打开它。由于我们没有提升权限,并且我们必须以管理员身份运行才能更改COM类注册信息,我们可以在注册表中启用DCOM反射,通过将AllowDCOMReflection写入HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft.NETFramework键,DWORD值为1,然后在受保护进程中加载.NET框架。
需要采取以下步骤来实现注入:
- 在注册表中启用DCOM反射。
- 添加TreatAs键以将StdFont重定向到System.Object COM类。
- 创建WaaSRemediationAgent对象。
- 使用类型库获取StdFont类类型信息。
- 使用CreateInstance方法创建StdFont对象,这将真正加载.NET框架并返回System.Object类的实例。
- 使用.NET反射调用System.Reflection.Assembly::Load方法与字节数组。
- 在加载的程序集中创建对象以强制代码执行。
- 清理所有注册表更改。
您需要在非.NET语言中执行这些步骤,否则序列化机制将启动并在调用进程中重新创建反射对象。我用C++编写了我的PoC,但您可能可以从Python等语言中完成。我不会提供PoC,但代码与我为CVE-2014-0257编写的漏洞利用非常相似,这将为您提供如何在C++中使用DCOM反射的示例。还要注意,.NET COM对象的默认值是使用v2框架运行它们,该框架不再默认安装。我不想弄乱v4,只是从Windows组件安装程序安装了v2。
我的PoC在Windows 10上第一次工作,但不幸的是,当我在Windows 11 24H2上运行它时,它失败了。我可以创建.NET对象,但调用对象上的任何方法都失败,错误为TYPE_E_CANTLOADLIBRARY。我可以在这里停止,已经证明了我的观点,但我想知道在Windows 11上是什么失败了。让我们最后深入探讨一下,看看我们是否能做一些事情来使其在最新版本的Windows上工作。
Windows 11的问题
我能够证明问题与受保护进程相关,如果我更改服务注册以无保护运行,则PoC工作。因此,必须有一些东西在专门在受保护进程中运行时阻止库的加载。这似乎一般不影响类型库,stdole的加载工作良好,因此是特定于.NET的。
在用Process Monitor检查PoC的行为后,很明显mscorlib.tlb库正在被加载以在服务器中实现存根类。由于某种原因,它未能加载,这阻止了存根的创建,进而导致任何调用失败。在这一点上,我知道发生了什么。在上一篇博客文章中,我讨论了通过修改用于创建接口存根的类型库来攻击NGEN COM进程,以引入类型混淆。这允许我覆盖KnownDlls句柄并强制任意DLL加载到内存中。我从Clément Labro和其他人的工作中知道,大多数围绕KnownDlls的攻击现在都被阻止了,但我怀疑对类型库类型混淆技巧也有某种修复。
深入挖掘oleaut32.dll,我找到了有问题的修复,VerifyTrust方法如下所示:
|
|