设置异步调用的安全性

异步调用存在严重安全风险,因为对接收器的回调可能不是原始应用程序或脚本发出的异步调用的结果。 远程连接的安全性基于客户端与远程计算机上提供程序之间的通信加密。 在 C++ 中,可以通过 CoInitializeSecurity 调用中的身份验证级别参数设置加密。 在脚本中,在名字对象连接中或在 SWbemSecurity 对象上设置 AuthenticationLevel。 有关详细信息,请参阅 使用 VBScript 设置默认进程安全级别

异步调用存在安全风险,因为在回调成功之前,WMI 会降低回调的身份验证级别。 在传出的异步调用中,客户端可以在与 WMI 的连接上设置身份验证级别。 WMI 检索客户端调用的安全设置并尝试使用相同的身份验证级别进行回调。 回调始终在 RPC_C_AUTHN_LEVEL_PKT_PRIVACY 级别发起。 如果回调失败,WMI 会将身份验证级别降低至回调可以成功的级别,如有必要,可降低至 RPC_C_AUTHN_LEVEL_NONE。 在身份验证服务不是 Kerberos 的本地系统内的调用上下文中,回调始终以 RPC_C_AUTHN_LEVEL_NONE 级别返回。

最低身份验证级别是 RPC_C_AUTHN_LEVEL_PKT(对于脚本为 wbemAuthenticationLevelPkt)。 但是,你可以指定更高级别,例如 RPC_C_AUTHN_LEVEL_PKT_PRIVACY (wbemAuthenticationLevelPktPrivacy)。 建议客户端应用程序或脚本将身份验证级别设置为 RPC_C_AUTHN_LEVEL_DEFAULT (wbemAuthenticationLevelDefault),以便可以将身份验证级别协商为服务器指定的级别。

HKEY_LOCAL_MACHINE\Software\Microsoft\WBEM\CIMOM\UnsecAppAccessControlDefault 注册表值控制 WMI 是否检查回调中可接受的身份验证级别。 这是唯一一个用于保护脚本或 Visual Basic 中发出的异步调用的接收器安全性的机制。 默认情况下,此注册表项设置为 0。 如果该注册表项为 0,则 WMI 不会验证身份验证级别。 若要保护脚本中的异步调用,请将该注册表项设置为 1。 C++ 客户端可以调用 IWbemUnsecuredApartment::CreateSinkStub 来控制对接收器的访问。 默认情况下,该值是在任意位置创建的。

以下主题提供了设置异步调用安全性的示例:

在 C++ 中设置异步调用安全性

IWbemUnsecuredApartment::CreateSinkStub 方法类似于 IUnsecuredApartment::CreateObjectStub 方法,它在单独的进程 Unsecapp.exe 中创建接收器以接收回调。 但是,CreateSinkStub 方法具有一个用于指定单独进程如何处理访问控制的 dwFlag 参数。

dwFlag 参数指定 Unsecapp.exe 的以下操作之一:

  • 使用注册表项设置来确定是否检查访问权限。
  • 忽略注册表项且始终检查访问权限。
  • 忽略注册表项且永不检查访问权限。

本主题中的代码示例需要以下 #include 语句才能正确编译。

#include <wbemidl.h>

以下过程说明如何使用 IWbemUnsecuredApartment 执行异步调用。

使用 IWbemUnsecuredApartment 执行异步调用

  1. 通过调用 CoCreateInstance 创建专用进程。

    以下代码示例调用 CoCreateInstance 来创建专用进程。

    CLSID                    CLSID_WbemUnsecuredApartment;
    IWbemUnsecuredApartment* pUnsecApp = NULL;
    
    CoCreateInstance(CLSID_WbemUnsecuredApartment, 
                     NULL, 
                     CLSCTX_LOCAL_SERVER, 
                     IID_IWbemUnsecuredApartment, 
                     (void**)&pUnsecApp);
    
  2. 实例化接收器对象。

    以下代码示例创建一个新的接收器对象。

    CMySink* pSink = new CMySink;
    pSink->AddRef();
    
  3. 为接收器创建一个存根。

    存根是从接收器生成的包装函数。

    以下代码示例为接收器创建存根。

    LPCWSTR          wszReserved = NULL;           
    IWbemObjectSink* pStubSink   = NULL;
    IUnknown*        pStubUnk    = NULL; 
    
    pUnsecApp->CreateSinkStub(pSink,
                              WBEM_FLAG_UNSECAPP_CHECK_ACCESS,  //Authenticate callbacks regardless of registry key
                              wszReserved,
                              &pStubSink);
    
  4. 释放接收器对象指针。

    可以释放对象指针,因为存根现在拥有该指针。

    以下代码示例释放对象指针。

    pSink->Release();
    
  5. 在任何异步调用中使用存根。

    调用完成后,释放本地引用计数。

    以下代码示例在异步调用中使用存根。

    // pServices is an IWbemServices* object
    pServices->CreateInstanceEnumAsync(strClassName, 0, NULL, pStubSink);
    

    有时可能必须在调用后取消异步调用。 如果需要取消调用,请使用最初发出调用的同一指针进行取消。

    以下代码示例说明如何取消异步调用。

    pServices->CancelAsyncCall(pStubSink);
    
  6. 使用完异步调用后,释放本地引用计数。

    确保仅在确认不需要取消异步调用后才释放 pStubSink 指针。 此外,在 WMI 释放 pSink 接收器指针后,不要释放 pStubSink。 在 pSink 之后释放 pStubSink 会创建一个循环引用计数,其中接收器和存根将永久保留在内存中。 相反,可能会在 IWbemObjectSink::SetStatus 调用中释放指针,该调用由 WMI 发出来报告原始异步调用已完成。

  7. 完成后,通过调用 Release() 来取消初始化 COM。

    以下代码示例演示如何对 pUnsecApp 指针调用 Release()

    pUnsecApp->Release();
    

有关 CoInitializeSecurity 函数和参数的详细信息,请参阅 COM 文档。