英文:
(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
我成功解决了这个问题。如果您没有使用当前登录用户令牌,您必须将CoSetProxyBlanket
的pAuthInfo
参数指定为有效的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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论