Search code examples
c++winapiwmi

(WMI) IWbemServices::Release() throws "Access Denied" exception when connected to remote machine


In C++, I can't find a way to appropriately terminate a WMI session with a remote server. Any attempt to release the IWbemServices pointer throws an exception; the TCP connection to the server remains established until the process exits (it's open after the last CoUninitialize call). This problem (thrown exception) does not occur when connecting to the local machine.

I've looked at a similar question asked here, but the solution from Microsoft (retrieving the IUnknown pointer and releasing it first) didn't solve the issue.

Here's the code (error checking has been omitted for readability):

HRESULT hRes = S_OK;

IWbemLocator* pWbemLocator = NULL;
IWbemServices* pWbemServices = NULL;

// these three pointers are already initialized...
PWCHAR wcUser; // L"theuser"
PWCHAR wcPass; // L"thepassword"
PWCHAR wcAuth; // L"ntlmdomain:THEDOMAIN"

std::wstring wstrNsPath = L"\\\\remoteserver\\ROOT\\CIMV2";

hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);

hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pWbemLocator);

// this returns S_OK :)
hRes = pWbemLocator->ConnectServer((BSTR)wstrNsPath.c_str(),
        (BSTR)wcUser,
        (BSTR)wcPass,
        NULL,
        WBEM_FLAG_CONNECT_USE_MAX_WAIT,
        (BSTR)wcAuth,
        NULL,
        &pWbemServices);

hRes = CoSetProxyBlanket(pWbemServices,
        RPC_C_AUTHN_DEFAULT,
        RPC_C_AUTHZ_DEFAULT,
        COLE_DEFAULT_PRINCIPAL,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        RPC_C_AUTHN_LEVEL_DEFAULT,
        NULL, // domain info already specified in 'wcAuth'
        EOAC_NONE);

// do some queries..

// cleanup
pWbemServices->Release(); // on remote sessions, this throws an exception
pWbemLocator->Release();
CoUninitialize();

The exception is displayed in the debug output (Visual Studio):

onecore\com\combase\dcomrem\call.cxx(1234)\combase.dll!0000ABCDEFABCDEF: (caller: 0000FEDCBAFEDCBA) ReturnHr(1) tid(4321) 80070005 Access is denied.

Is this expected behavior? Should the connection between the client and server not be terminated once the session is released?

I attempted to follow MSDN's advice and added the following after the CoSetProxyBlanket() call in the code above. It didn't change anything.

IUnknown* pUnknown = NULL;
pWbemServices->QueryInterface(IID_IUnknown, (LPVOID*)&pUnknown);
if (pUnknown)
{
    hRes = CoSetProxyBlanket(pUnknown,
        RPC_C_AUTHN_DEFAULT,
        RPC_C_AUTHZ_DEFAULT,
        COLE_DEFAULT_PRINCIPAL,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        RPC_C_AUTHN_LEVEL_DEFAULT,
        NULL,
        EOAC_NONE);
    pUnknown->Release();
}

Any advice is greatly appreciated!

EDIT So after capturing session packets, it would appear that setting the proxy security with pAuthInfo == NULL causes the request to be made by the current logged on user of the client machine. It ignores the credentials that I provided when calling ConnectServer. I'm aware that the COAUTHIDENTITY structure allows you to pass the correct credentials to CoSetProxyBlanket, but I'd like to avoid having to input the domain as a separate variable. In other words, is there a way that this information can be extracted using the wcAuth when the request is made to a remote server? If so, how could I distinguish local vs. remote requests?

Here is the output from Wireshark that led me to believe this is the problem (see packet 1617):

No.     Time        Source      Destination Protocol Length Info
1615    162.221354  [CLIENT_IP] [SERVER_IP] DCERPC  174 Alter_context: call_id: 8, Fragment: Single, 1 context items: IRemUnknown2 V0.0 (32bit NDR), NTLMSSP_NEGOTIATE
1616    162.228517  [SERVER_IP] [CLIENT_IP] DCERPC  366 Alter_context_resp: call_id: 8, Fragment: Single, max_xmit: 5840 max_recv: 5840, 1 results: Acceptance, NTLMSSP_CHALLENGE
1617    162.229396  [CLIENT_IP] [SERVER_IP] DCERPC  612 AUTH3: call_id: 8, Fragment: Single, NTLMSSP_AUTH, User: .\[client_user]
1618    162.229495  [CLIENT_IP] [SERVER_IP] IRemUnknown2    182 RemRelease request Cnt=1 Refs=5-0
1619    162.235567  [SERVER_IP] [CLIENT_IP] TCP  60 49669 → 59905 [ACK] Seq=1606 Ack=4339 Win=64768 Len=0
1620    162.235567  [SERVER_IP] [CLIENT_IP] DCERPC  86  Fault: call_id: 8, Fragment: Single, Ctx: 0, status: nca_s_fault_access_denied

Solution

  • I was able to resolve the issue. If you are not using the current logged-on user token, you must specify the pAuthInfo parameter of CoSetProxyBlanket to a valid COAUTHIDENITY structure pointer. The docs for IWbemLocator::ConnectServer actually state that it's best practice to include the domain in the strUser parameter... and that if you do so, you must pass the authority string as NULL.

    One thing to note is that if ConnectServer succeeds, you don't have to go crazy with the sanitizing of the username string for correctness; the login either worked or it didn't (and breaks/throws an exception, depending on how you handle errors). In other words, just search the string for the domain delimiters ('\\' or '@') and split them appropriately into domain name and username.