-
-
[翻译] 利用 Windows UPnP Device Host 服务和 Update Orchestrator 服务 实现本地 SYSTEM 提权
-
发表于: 2020-1-14 19:24 7274
-
[翻译] 利用 Windows UPnP Device Host 服务和 Update Orchestrator 服务 实现本地 SYSTEM 提权
介绍
这篇博客文章讨论了 NCC Group 顾问在通过COM本地服务进行特权提升研究期间发现的两个漏洞。这些漏洞中的第一个(CVE-2019-1405)是COM服务中的逻辑错误,允许本地非特权用户以 LOCAL SERVICE
用户身份执行任意命令。第二个漏洞(CVE-2019-1322)是一个简单的服务错误配置,允许本地 SERVICE
组中的任何用户重新配置作为 SYSTEM
执行的服务(此漏洞也由其他研究人员独立发现)。组合使用这些漏洞后,这些漏洞允许未经授权的本地用户作为 SYSTEM
用户在默认安装的Windows 10上执行任意命令。
COM背景
我们将从一些背景资料开始,这些背景资料应有助于要讨论的第一个漏洞。如果您熟悉 Windows 上 COM 的基本概念,可以跳过本节。
组件对象模型(Component Object Model,COM)是微软在1993年推出的用于软件组件的二进制接口标准。从广义上讲,这意味着COM是一种允许开发人员不限定于特定语言的方式使用某些软件组件的架构。COM的普遍用法包括 ActiveX 和OLE(例如,在 Word 文档中嵌入 Excel 电子表格),但是微软和许多第三方应用程序在整个 Windows 生态系统内部都使用COM,它确实无处不在!
COM对象通过存储在 Windows 注册表中的全局唯一标识符(GUID)即 CLSID 来标识(许多 COM 对象也在注册表中命名,但该名称仅链接到相应的 CLSID )。CLSID 条目包含创建对象实例时COM子系统使用的信息。
COM 通过公开一个或多个接口来隐藏对象的实现细节。从本质上说,接口定义了一组对象支持的方法,而没有规定有关实现的任何内容。该接口在调用方法的代码与实现该方法的代码之间划了清楚的界限。每个 COM 对象都支持 IUnknown 接口,该接口用于通过 AddRef,Release和 QueryInterface 方法向其他受支持的接口提供引用计数和转换。接口通常通过微软接口定义语言(MIDL)定义,并通过称为 IID 的 GUID 进行标识。重申一下将它们存储在Windows注册表中。但是请注意,注册表中未列出给定 COM 对象支持的特定接口。
大体上说,COM对象可分为两类:调用进程时创建的和进程外创建的。针对特权提升攻击,进程外创建的COM对象作为本地服务执行是一个明显的攻击面,一个默认的Winows系统有很多这样的COM对象。
有许多工具可用于检查在给定 Windows 实例上注册的 COM 对象和接口,其中最好的也许是 Google Project Zero 的 James Forshaw 写的 OleViewDotNet [1]。我们不会在这篇文章中介绍这个工具的许多功能,但是如果您有兴趣学习有关 COM 的更多知识,可以使用 OleViewDotNet 并阅读 James 在这个主题的写的任何内容,这将会是一个很好的开始!
UPnP设备主机服务(CVE-2019-1405)
UPnP设备主机服务在Windows客户端操作系统(从Windows XP到Windows 10)上默认启用,并以用户 NT AUTHORITY\LOCAL SERVICE 的身份执行(此服务默认情况下安装在 Windows 服务器版本上,但某些版本上默认未启用)。许多 COM 对象在此服务中作为本地服务器执行,如下面的屏幕截图中的 OleViewDotNet 所示:
与 Windows 中的任何安全对象一样,可以将访问控制应用于 COM 服务器,OleViewDotNet 可以显示此信息。下面的屏幕截图显示了应用于 UPnP 设备主机服务的启动权限:
在这种情况下,DACL 会阻止网络用户在此服务中启动 COM 对象,但是所有本地用户都被明确允许这样做,这显然是特权提升的一个攻击面。
此服务托管的 UPnPContainerManager
和 UPnPContainerManager64
COM 对象都实现了 IUPnPContainerManager
接口。这个接口没有文档记录,但是OleViewDotNet
能够恢复有关其公开方法的一些信息。此外,微软为核心操作系统组件提供了调试符号,从而为接口(作为MIDL的一个片段)提供了以下初始的工作定义:
[ object, uuid(6d8ff8d4-730d-11d4-bf42-00b0d0118b56), pointer_default(unique) ] interface IUPnPContainerManager : IUnknown { HRESULT ReferenceContainer([in] wchar_t* string1); HRESULT UnReferenceContainer([in] wchar_t* string1); HRESULT CreateInstance( [in] wchar_t* string1, [in] GUID* guid1, [in] GUID* guid2, IUnknown** pObject); HRESULT CreateInstanceWithProgID( [in] wchar_t* string1, [in] wchar_t* guid1, [in] GUID* guid2, [out] IUnknown** pObject); HRESULT Shutdown(); }
乍一看,由 IUPnPContainerManager
公开的 CreateInstance
方法似乎特别有趣。对于那些具有一定 COM 编程经验的人来说,这个方法的名称和 API 都应建议与用于创建任何 COM 对象的标准 CoCreateInstance
方法[2]可能存在关系(尽管第一个参数的用途尚不清楚)。不幸的是,使用众所周知的 CLSID 和 IID 以及第一个参数作为伪字符串调用 CreateInstance
会导致出现未记录的错误代码。
此时我们别无选择,只能反汇编程序,然后看一下实现该方法的代码。幸运的是,识别这段代码很简单(OleViewDotNet
既可以识别实现感兴趣的类的模块,又可以识别接口方法的偏移量)并且我们发现大部分工作是在 upnphost.dll
模块中实现的 CContainerManagerBase::CreateInstance
方法中执行的。下面的屏幕截图显示了这个方法的反编译代码:
传递给 CreateInstance
的字符串参数(在这段代码中标记为 a2
)被复制到 v7
,然后显然通过调用 HrAssign
用于初始化 Block
对象。然后将 Block
传递给HrLookup
,如果调用成功,则实际工作似乎由第33行的虚函数调用执行。调试这段代码很快发现,当一个伪字符串作为 CreateInstance
的第一个参数传递时,我们无法检查HrLookup
的结果。因此,显然该参数的内容很重要。
自然而然的下一步是拆解 HrLookup
函数,但是事实证明这并非易事,我们不得不重新考虑。 记住在实践中我们通过IUPnPContainerManager
接口与这段代码进行交互,我们决定查看其他公开的方法以获取可能的灵感。 具体来说,下面的屏幕截图展示了实现了 ReferenceContainer
的方法 CContainerManagerBase::ReferenceContainer
的反编译代码:
这个方法的开始看上去与CreateInstance
的实现非常相似,但是在这种情况下,如果对 HrLookup
的调用失败,我们最终将调用 HrInsertTransfer
方法,并将从字符串参数创建的Block
对象传递给 ReferenceContainer
。 此时的自然假设是,在调用 CreateInstance
之前,我们可能需要调用 ReferenceContainer
,并且快速测试表明这确实有效。 基于此知识, IUPnPContainerManager
接口更好的工作定义如下所示:
[ object, uuid(6d8ff8d4-730d-11d4-bf42-00b0d0118b56), pointer_default(unique) ] interface IUPnPContainerManager : IUnknown { HRESULT ReferenceContainer([in] wchar_t* containerName); HRESULT UnReferenceContainer([in] wchar_t* containerName); HRESULT CreateInstance( [in] wchar_t* containerName, [in] GUID* clsid, [in] GUID* iid, IUnknown** pObject); HRESULT CreateInstanceWithProgID( [in] wchar_t* containerName, [in] wchar_t* progID, [in] GUID* iid, [out] IUnknown** pObject); HRESULT Shutdown(); }
使用任意字符串调用 ReferenceContainer
方法,然后使用相同字符串调用 CreateInstance
方法,将在本地服务器进程中创建由 clsid
参数标识的 COM
对象的实例,并将对此对象中由 iid
标识的接口的引用返回给对象参数。
为什么这么有趣?实际上,这允许低特权本地用户使用任何已注册的进程内 COM 对象,就好像它是作为用户 NT AUTHORITY\LOCAL SERVICE
执行的进程外本地服务器一样。特别是,可以创建 Windows Script Host Shell 对象的实例并获得对该对象的 IWshShell
接口的引用。然后,可以通过从该接口调用 Run
方法,从 UPnP 设备主机进程的上下文中执行任意命令。
在 Windows 10 上,UPnP设备主机服务配置为在没有模拟权限的情况下作为用户 NT AUTHORITY\LOCAL SERVICE
执行,ServiceSIDType
设置为 SERVICE_SID_TYPE_UNRESTRICTED
。下面的 Process Explorer 屏幕截图显示了托管 UPnP 设备主机服务的 svchost 实例的过程属性,并确认 SeImpersonatePrivilege
未启用 :
不幸的是,这阻止了通过众所周知的方法(例如,在[3]和[4]中记录的方法)提升到 NT AUTHORITY\SYSTEM
。 但是,服务用户具有比标准低特权用户更高的特权,例如 NT AUTHORITY\SERVICE
组的成员身份(在下面讨论第二个漏洞时,这将是我们特别感兴趣的)。
在Windows XP上,这种细粒度的服务配置是不可能的,并且直接提升到 NT AUTHORITY\SYSTEM
是非常简单的。
Update Orchestrator服务(CVE-2019-1322)
发现了上述漏洞之后,我们自然很好奇所获得的权限是否允许我们能够利用另一个漏洞获得对主机的完全控制。 对应用于可能使用的对象的默认访问控制进行扫描后,便迅速识别出 Update Orchestrator 服务。
Update Orchestrator 服务以 NT AUTHORITY\SYSTEM
的身份运行,并且默认情况下在 Windows 10 和 Windows Server 2019 上启用。使用 SysInternals 工具 accesschk.exe
得到下面的输出,显示了在 Windows 10版本1803 到1903(当然是在修补之前)上对 Update Orchestrator(UsoSvc)启用的显式访问控制:
UsoSvc Medium Mandatory Level (Default) [No-Write-Up] R NT AUTHORITY\Authenticated Users SERVICE_QUERY_STATUS SERVICE_QUERY_CONFIG SERVICE_INTERROGATE SERVICE_ENUMERATE_DEPENDENTS SERVICE_START SERVICE_USER_DEFINED_CONTROL R BUILTIN\Administrators SERVICE_QUERY_STATUS SERVICE_QUERY_CONFIG SERVICE_INTERROGATE SERVICE_ENUMERATE_DEPENDENTS SERVICE_START SERVICE_STOP SERVICE_USER_DEFINED_CONTROL RW NT AUTHORITY\SYSTEM SERVICE_ALL_ACCESS RW NT AUTHORITY\SERVICE SERVICE_ALL_ACCESS
输出的最后两行显示,组 NT AUTHORITY\SERVICE
中的用户具有对该服务的完全访问权限。 特别是,该组中的用户能够停止、重新配置和启动服务,这允许此类用户可以执行诸如 NT AUTHORITY\SYSTEM
之类的任意命令。
例如,以下命令(作为NT AUTHORITY\SERVICE
组中的用户身份执行时)会将名为_tmpAdmUser
的管理用户(密码为H.jqt41Kz!a!
)添加到易受攻击的主机,并将服务还原到默认状态:
sc stop UsoSvc sc config UsoSvc binpath= "cmd.exe /c net user /add _tmpAdmUser H.jqt41Kz!a! &" sc start UsoSvc sc stop UsoSvc sc config UsoSvc binpath= "cmd.exe /c net localgroup administrators /add _tmpAdmUser &" sc start UsoSvc sc stop UsoSvc sc config UsoSvc binpath= "C:\WINDOWS\system32\svchost.exe -k netsvcs -p" sc start UsoSvc
检查选定的Windows服务,结果表明作为LOCAL SERVICE
或 NETWORK SERVICE
运行的所有服务都能够执行此攻击。 尤其是,上面描述的UPnP设备主机服务能够执行此攻击,允许通过链接CVE-2019-1405和CVE-2019-1322将权限从任何本地用户提升到Windows 10(版本1803至1903)上的系统
用户。
补丁
CVE-2019-1322于2019年10月通过从SERVICE
组中的用户移除对 Update Orchestrator Service 的完全控制而得到解决。
CVE-2019-1405于2019年11月通过在方法ReferenceContainer
、CreateInstance
和CreateInstanceWithProgID
中实现访问检查来解决,以确保调用方是Administrators组的成员。
参考
[1] https://github.com/tyranid/oleviewdotnet
[2] https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance
[3] https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/
[4] https://bugs.chromium.org/p/project-zero/issues/detail?id=325&redir=1
本文由看雪翻译小组 fyb波 编译
本文由看雪翻译小组 一壶葱茜 校对
[招生]系统0day安全班,企业级设备固件漏洞挖掘,Linux平台漏洞挖掘!