(WMI) IWbemServices::Release() 在连接到远程机器时抛出“拒绝访问”异常

huangapple go评论78阅读模式
英文:

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

问题

在C++中,我找不到一种适当终止与远程服务器的WMI会话的方法。释放IWbemServices指针的任何尝试都会引发异常;直到进程退出(在最后一个CoUninitialize调用后仍然打开),与服务器的TCP连接仍然保持连接。当连接到本地机器时,不会发生此问题(抛出异常)。

我已经查看了一个类似的问题,链接在这里,但微软的解决方案(检索IUnknown指针并首先释放它)未能解决此问题。

这是代码(出于可读性考虑,已省略了错误检查):

HRESULT hRes = S_OK;

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

// 这三个指针已经初始化...
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);

// 这返回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, // 域信息已在 'wcAuth' 中指定
        EOAC_NONE);

// 做一些查询..

// 清理
pWbemServices->Release(); // 在远程会话上,这会引发异常
pWbemLocator->Release();
CoUninitialize();

在调试输出(Visual Studio)中显示异常:

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

这是否是预期行为?一旦会话被释放,客户端和服务器之间的连接应该不会终止吗?

我尝试遵循MSDN的建议,在上述代码中的CoSetProxyBlanket()调用之后添加了以下内容,但没有改变任何东西。

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

非常感谢任何建议!

编辑 在捕获会话数据包后,似乎使用pAuthInfo == NULL设置代理安全性会导致请求由客户端计算机的当前登录用户发出。它会忽略我在调用ConnectServer时提供的凭据。我知道COAUTHIDENTITY结构允许您将正确的凭据传递给CoSetProxyBlanket,但我希望避免将域作为单独的变量输入。换句话说,是否有办法在向远程服务器发出请求时使用wcAuth来提取此信息?如果可以,我如何区分本地请求和远程请求?

以下是导致我认为这是问题的Wireshark输出(请参见数据包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
英文:

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

答案1

得分: -1

我成功解决了这个问题。如果您没有使用当前登录用户令牌,您必须将CoSetProxyBlanketpAuthInfo参数指定为有效的COAUTHIDENTITY结构指针。IWbemLocator::ConnectServer的文档实际上指出,在strUser参数中包含域是最佳实践...如果您这样做,必须将授权字符串设置为NULL

需要注意的是,如果ConnectServer成功,您不必过于疯狂地清理用户名字符串以确保正确性;登录要么成功,要么失败(并根据错误处理方式引发异常)。换句话说,只需搜索字符串以查找域分隔符('\'或'@')并适当地拆分它们为域名和用户名。

英文:

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.

huangapple
  • 本文由 发表于 2023年6月1日 12:37:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76378706.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定