CVE-2019-1405 and CVE-2019-1322 – Elevation to SYSTEM via the UPnP Device Host Service and the Update Orchestrator Service

Introduction

This blog post discusses two vulnerabilities discovered by NCC Group consultants during research undertaken on privilege elevation via COM local services. The first of these vulnerabilities (CVE-2019-1405) is a logic error in a COM service and allows local unprivileged users to execute arbitrary commands as a LOCAL SERVICE user. The second vulnerability (CVE-2019-1322) is a simple service misconfiguration that allows any user in the local SERVICE group to reconfigure a service that executes as SYSTEM (this vulnerability was independently also discovered by other researchers). When combined, these vulnerabilities allow an unprivileged local user to execute arbitrary commands as the SYSTEM user on a default installation of Windows 10.

COM Background

We will begin with some background material that should help frame the first vulnerability that we will discuss. Feel free to skip this section if you are comfortable with the basic concepts of COM on Windows.

Component Object Model (COM) is a binary-interface standard for software components introduced by Microsoft in 1993. Broadly speaking, this means COM is an architecture that allows developers to use certain software components in a language agnostic manner. Well known usages of COM include ActiveX and OLE (for example, embedding an Excel spreadsheet in a Word document), however COM is used internally across the entire Windows ecosystem by both Microsoft and many third party applications. It really is everywhere!

COM objects are identified via a Globally Unique Identifier (GUID) known as a CLSID, stored in the Windows registry (many COM objects are also named in the registry, but the name just links to the corresponding CLSID). A CLSID entry contains the information used by the COM subsystem when an instance of the object is created.

COM hides the implementation details of objects by exposing one or more interfaces. Essentially, an interface defines a set of methods that an object supports, without dictating anything about the implementation. The interface marks a clear boundary between code that calls a method and the code that implements the method. Every COM object supports the IUnknown interface, and this interface is used to provide reference counting and casting to other supported interfaces via the AddRefReleaseand QueryInterface methods. Interfaces are typically defined via the Microsoft Interface Definition Language (MIDL) and are identified via a GUID known as an IID. Once again, these are stored in the Windows registry. Note, however, that the specific interfaces supported by a given COM object are not listed in the registry.

Loosely speaking, COM objects fall into one of two categories – those that are created inside the calling process, and those that are created out of process. For the purposes of privilege elevation attacks, out of process COM objects executing as local services are an obvious attack surface and a default installation of Windows includes many such objects.

A number of tools are available to inspect the COM objects and interfaces registered on a given instance of Windows. Perhaps the best of these is OleViewDotNet [1], written by James Forshaw of Google Project Zero. We will not cover many of the features of this tool in this blog post, however if you are interested in learning more about COM, playing with OleViewDotNet and reading anything James has written on the subject would be a great place to start!

The UPnP Device Host Service (CVE-2019-1405)

The UPnP Device Host service is enabled by default on Windows client operating systems from Windows XP to Windows 10 and executes as the user NT AUTHORITY\LOCAL SERVICE (this service is installed by default on server versions of Windows, but is not enabled by default on some). A number of COM objects execute as local servers in this service, as shown by OleViewDotNet in the screenshot below:

Like any securable object in Windows, it is possible to apply access controls to COM servers and OleViewDotNet is able to display this information. The following screenshot shows the launch permissions applied to the UPnP Device Host service:

In this case, a DACL prevents network users from launching COM objects in this service, however all local users are explicitly allowed to do so and this is clearly on the attack surface for privilege escalation.

The UPnPContainerManager and UPnPContainerManager64 COM objects hosted by this service both implement the IUPnPContainerManager interface. This interface is not documented, however OleViewDotNet is able to recover some information about its exposed methods. Furthermore, Microsoft make debug symbols available for core operating system components, leading to the following initial working definition for the interface (as a fragment of 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();
}

At first glance, the CreateInstance method exposed by IUPnPContainerManager seems particularly interesting. For those of you with some COM programming experience, both the name and the API for this method should suggest a possible relationship to the standard CoCreateInstance method [2] used to create any COM object (although the purpose of the first argument is not immediately clear). Unfortunately, calling CreateInstance with a well-known CLSID and IID and a dummy string as the first parameter results in an undocumented error code.

At this point, we were left with little choice but to break out a disassembler and take a look at the code implementing the method. Fortunately, identifying this code is trivial (OleViewDotNet identifies both the module that implements the class of interest and the offsets of interface methods) and we found that the bulk of the work is performed in the CContainerManagerBase::CreateInstance method implemented in the upnphost.dll module. The following screenshot shows a decompilation of this method:

The string parameter passed to CreateInstance, labelled a2 in this code, is copied to v7 and then apparently used to initialise the Block object via a call to HrAssignBlock is then passed to HrLookup and if this call succeeds, the real work then appears to be performed by the virtual function call at line 33. Debugging this code quickly revealed that when a dummy string is passed as the first argument to CreateInstance, we fail checking the result of HrLookup. Clearly, therefore, the contents of this argument are important.

A natural next step was to pull apart the HrLookup function, however this turned out to be non-trivial and we were forced to reconsider. Remembering that in practice we interact with this code via the IUPnPContainerManager interface, we decided to take a look at the other exposed methods for possible inspiration. In particular, the following screenshot shows a decompilation of the method CContainerManagerBase::ReferenceContainer that implements ReferenceContainer:

The start of this method looks quite similar to the implementation of CreateInstance, however in this case if the call to HrLookup fails, we ultimately call the HrInsertTransfer method, passing in the Block object created from the string argument to ReferenceContainer. A natural assumption at this point is that prior to calling CreateInstance we may need to call ReferenceContainer and a quick test showed that this does indeed work. A better working definition for the IUPnPContainerManagerinterface based upon this knowledge is shown below:

[
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();
}

A call to the ReferenceContainer method with an arbitrary string followed by a call to the CreateInstance method with the same string causes an instance of the COM object identified by the clsid parameter to be created in the local server process and a reference to the interface identified by iid from this object is returned to the caller in the pObject parameter.

So why is this interesting? Essentially, this allows a low privilege local user to use any registered in-process COM object as if it were an out of process local server executing as the user NT AUTHORITY\LOCAL SERVICE. In particular, it is possible to create an instance of a Windows Script Host Shell object and obtain a reference to the IWshShell interface of this object. Arbitrary commands may then be executed from the context of the UPnP Device Host process via calls to the Run method from this interface.

On Windows 10, the UPnP Device Host service is configured to execute without impersonation privileges as the user NT AUTHORITY\LOCAL SERVICE with a ServiceSidType set to SERVICE_SID_TYPE_UNRESTRICTED. The following screenshot from Process Explorer shows the process properties for an instance of svchost hosting the UPnP Device Host service and confirms that SeImpersonatePrivilege is not enabled:

Unfortunately, this prevents elevation to NT AUTHORITY\SYSTEM via well-known methods such as those documented at [3] and [4]. The service user has additional privileges over those of a standard low-privileged user, however, such as membership of the NT AUTHORITY\SERVICE group (which will be of particular interest below when we discuss our second vulnerability).

On Windows XP, such fine grained configuration of services is not possible and immediate elevation to NT AUTHORITY\SYSTEM is straightforward.

The Update Orchestrator Service (CVE-2019-1322)

Having discovered the vulnerability described above, we were naturally curious to see if the privileges obtained would allow us to exploit another vulnerability to gain full control of a host. A scan of the default access controls applied to objects that might be of use quickly identified the Update Orchestrator Service.

The Update Orchestrator Service runs as NT AUTHORITY\SYSTEM and is enabled by default on Windows 10 and Windows Server 2019. The following output from a call to the SysInternals tool accesschk.exe shows the explicit access controls enabled on the Update Orchestrator Service (UsoSvc) on Windows 10 versions 1803 to 1903 (prior to patching, of course!):

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

The final two lines from this output show that users in the group NT AUTHORITY\SERVICE have full access to the service. In particular, users in this group are able to stop, reconfigure and start the service and this allows such a user to execute arbitrary commands as NT AUTHORITY\SYSTEM.

For example, the following commands (when executed as a user in the group NT AUTHORITY\SERVICE) will add an administrative user named _tmpAdmUser (with password H.jqt41Kz!a!) to a vulnerable host and restore the service to its default state:

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

A selection of Windows services were examined and the results indicate that all services running as either LOCAL SERVICE or NETWORK SERVICE are able to perform this attack. In particular, the UPnP Device Host service described above is able to perform this attack, allowing elevation of privilege from any local user to the SYSTEM user on Windows 10 (versions 1803 to 1903) by chaining CVE-2019-1405 and CVE-2019-1322.

Patches

CVE-2019-1322 was addressed in October 2019 by removing full control to the Update Orchestrator Service from users in the SERVICE group.

CVE-2019-1405 was addressed in November 2019 by implementing access checks in the methods ReferenceContainerCreateInstance and CreateInstanceWithProgID to ensure that the caller is a member of the Administrators group.

References

[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

Published date:  12 November 2019

Written by:  Phillip Langlois and Edward Torkington