0x00 前言
在本文中,我们将讨论NCC Group在分析COM本地服务时找到的两个漏洞。第一个漏洞(CVE-2019-1405)是COM服务中的一个逻辑错误,可以允许本地非特权用户以LOCAL SERVICE
用户身份执行任意命令。第二个漏洞(CVE-2019-1322)属于比较简单的服务错误配置,允许本地SERVICE
组中的任意用户重新配置以SYSTEM
权限运行的服务(其他研究者也独立发现了这个漏洞)。这两个漏洞结合起来后,本地低特权用户就可以使用SYSTEM
用户身份在默认安装的Windows 10系统上执行任意命令。
0x01 COM介绍
首先我们介绍一下相关背景,以便我们理解第一个漏洞。如果大家对Windows上的COM比较理解,可以直接跳过这部分内容。
COM(Component Object Model,组件对象模型)是用于软件组件的一个二进制接口标准,由微软在1993年引入。从广义上讲,这意味着COM是一个架构,允许开发者以与语言无关的方式使用特定的软件组件。COM有许多知名的应用,包括ActiveX及OLE(比如在Word文档中嵌入Excel电子表格)。此外,微软及第三方应用会在整个Windows内部生态中使用COM,应用非常广泛。
COM对象通过GUID(全局唯一标识符)进行标识,即CLSID,CLSID存储在Windows注册表中(许多COM对象也会直接在注册表中命名,但名称还是会链接到对应的CLSID)。CLSID表项中包含COM子系统在对象实例创建时所使用的相关信息。
COM会对外公开一个或多个接口,隐藏对象的内部实现。接口本质上定义的是对象支持的一系列方法,而没有提供具体的实现方式。我们可以将接口当成调用方法的代码与实现该方法的代码之间的一个边界。每个COM对象都支持IUnknown
接口,该接口用来提供引用计数,也可以通过AddRef
、Release
及QueryInterface
方法强制转换为其他接口。接口通常通过MIDL(Microsoft Interface Definition Language,微软接口定义语言)进行定义,并且通过某种GUID(即IID
)进行标识,这些信息也都存放在注册表中。然而这里需要注意的是,对于特定的COM对象,注册表中并没有提供该对象能支持的特定接口信息。
COM对象大致可以分为两大类:在调用进程中创建的对象以及在进程外创建的对象。在权限提升攻击中,以本地服务方式执行的进程外COM对象是绝佳的攻击目标,并且默认安装的Windows中存在许多这类对象。
我们可以使用许多工具来检查COM对象以及在指定Windows实例上注册的接口。可能最好的工具为OleViewDotNet,这里我们就不赘述该工具的各种功能,如果大家对COM比较感兴趣,可以使用一下OleViewDotNet
,阅读James写过的关于这方面内容的各种研究文章。
0x02 CVE-2019-1405:UPnP Device Host服务
UPnP Device Host服务从Windows XP到Windows 10系统中默认都处于启用状态,并且以NT AUTHORITY\LOCAL SERVICE
用户权限运行(该服务默认安装在Server版的Windows系统中,但某些系统下默认没有启用)。该服务中,有一些COM对象以本地服务端方式运行,在OleViewDotNet
中的分析结果如下所示:
与Windows中的其他安全对象一样,访问控制可以应用于COM服务端,OleViewDotNet
也能显示相关信息。如下图所示,我们可以看到应用于UPnP Device Host服务的启动权限:
在这种情况下,DACL会阻止NETWORK
用户启动该服务中的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来猜测该方法可能与标准的CoCreateInstance
有关联,而后者可以用来创建任何COM对象(虽然目前我们还不是特别明确CreateInstance
第一个参数的作用)。不幸的是,如果我们使用已知的CLSID、IID以及使用某个字符串作为第一个参数来调用CreateInstance
,会出现未公开的错误代码。
此时我们没有特别多的选择,只能打开反编译器,观察该方法的内部代码实现。幸运的是,找到该代码很简单(OleViewDotNet
可以帮我们识别实现目标类的模块以及方法接口的偏移量),我们发现大部分工作由CContainerManagerBase::CreateInstance
方法完成,该方法的实现位于upnphost.dll
模块中,部分反编译结果如下图所示:
传入CreateInstance
的字符串参数在上述代码中为a2
,该参数会被拷贝到v7
,然后通过调用HrAssign
来显式初始化Block
对象。Block
随后会被传递给HrLookup
,如果HrLookup
成功返回,那么实际的处理逻辑似乎会由第33行的函数调用来完成。调试代码后,我们很快就可以发现,当使用某个字符串作为第一个参数传入CreateInstance
,HrLookup
函数无法返回成功值。因此,该字符串参数的具体内容非常重要。
接下来自然是分析HrLookup
函数,经过努力后,我们发现这个任务并不是那么简单,我们只能另辟蹊径。前面提到过,我们通过IUPnPContainerManager
接口来与该代码交互,因此我们决定查看其他公开的方法来获取灵感。这里我们以CContainerManagerBase::ReferenceContainer
方法为突破口,该方法实现了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对象实例,并且该方法会通过pObject
参数,向调用方返回由iid
参数标识的接口。
为什么这种操作比较有趣?这种操作允许低权限本地用户以NT AUTHORITY\LOCAL SERVICE
用户身份来使用已注册的任何进程内COM对象,从而实现类似进程外调用的效果。更具体一点,攻击者可能创建Windows Script Host Shell对象的实例,获得该对象IWshShell
接口的引用。随后,攻击者可能调用该接口的Run
方法,在UPnP Device Host进程上下文中执行任意命令。
在Windows 10上,UPnP Device Host服务不需要模拟,会以AUTHORITY\LOCAL SERVICE
用户身份执行,ServiceSidType
的值设置为SERVICE_SID_TYPE_UNRESTRICTED
。我们可以使用Process Explorer观察托管UPnP Device Host服务的svchost进程属性,确认SeImpersonatePrivilege
处于未启用状态。
不幸的是,我们无法通过已知的一些方法(如[1]及[2]这两处参考资料)提升至NT AUTHORITY\SYSTEM
权限。服务账户比标准低权限用户具备更多的权限,例如服务账户还隶属于NT AUTHORITY\SERVICE
组(这一点在讨论第二个漏洞时比较关键)。
在Windows XP上,系统无法实现这种细粒度的服务配置,因此我们可以直接提升至NT AUTHORITY\SYSTEM
。
0x03 CVE-2019-1322:Update Orchestrator服务
找到上一个漏洞后,我们想知道是否可以利用已获取的权限来利用另一个漏洞,实现对目标主机的完全控制。快速扫描可被利用对象上默认的访问控制策略后,我们找到了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\SERVICE
权限执行任意命令。
比如,我们可以以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 Device Host服务,这样我们就能以本地任意用户身份,结合使用CVE-2019-1405及CVE-2019-1322这两个漏洞,成功在Windows 10(1803到1903)系统上将权限提升至SYSTEM
用户。
0x04 补丁情况
微软在2019年10月修复了CVE-2019-1322,删除了SERVICE
组中用户对Update Orchestrator服务的完整控制权限。
此外,微软在2019年11月修复了CVE-2019-1405,在ReferenceContainer
、CreateInstance
以及CreateInstanceWithProgID
中添加了访问检测机制,确保调用方隶属于管理员组。
发表评论
您还未登录,请先登录。
登录