COM Elevation 名字对象

COM 提升名字对象允许正利用用户帐户控制 (UAC) 运行的应用程序使用提升的特权激活 COM 类。 有关详细信息,请参阅关注最低特权

何时使用提升名字对象

提升名字对象用于激活 COM 类来完成需要提升权限的特定有限函数,例如更改系统日期和时间。

提升需要来自 COM 类及其客户端的参与。 COM 类必须按照“要求”部分中所述,通过批注其注册表项来支持提升。 COM 客户端必须使用提升名字对象请求提升。

提升名字对象不旨在提供应用程序兼容性。 例如,如果要将旧版 COM 应用程序(如 WinWord)作为提升的服务器运行,则应将 COM 客户端可执行文件配置为需要提升,而不是使用提升名字对象激活旧应用程序的类。 当提升的 COM 客户端使用旧应用程序的 CLSID 调用 CoCreateInstance 时,客户端的提升状态将流向服务器进程。

并非所有 COM 功能都与提升兼容。 不起作用的功能包括:

  • 提升不会从客户端流向远程 COM 服务器。 如果客户端使用提升名字对象激活远程 COM 服务器,则即使它支持提升,服务器也不会提升。
  • 如果提升的 COM 类在 COM 调用期间使用模拟,则可能会在模拟期间失去提升的权限。
  • 如果提升的 COM 服务器在正在运行的对象表中注册类 (ROT),该类将不适用于非提升的客户端。
  • 使用 UAC 机制提升的进程不会在 COM 激活期间加载每用户类。 对于 COM 应用程序,这意味着如果非特权帐户和特权帐户都要使用应用程序,则应用程序的 COM 类必须安装在 HKEY_LOCAL_MACHINE 注册表配置单元中。 如果特权帐户从未使用应用程序,则只需在 HKEY_USERS 配置单元中安装应用程序的 COM 类。
  • 不允许从非提升到提升的应用程序拖放。

要求

若要使用提升名字对象激活 COM 类,必须将该类配置为作为启动用户运行或“激活即激活器”应用程序标识。 如果类配置为在任何其他标识下运行,激活将返回错误 CO_E_RUNAS_VALUE_MUST_BE_AAA。

该类还必须使用与多语言用户界面 (MUI) 兼容的“友好”显示名称进行批注。 这需要以下注册表项:

HKEY_LOCAL_MACHINE\Software\Classes\CLSID
   {CLSID}
      LocalizedString = displayName

如果缺少此项,激活将返回错误 CO_E_MISSING_DISPLAYNAME。 如果缺少 MUI 文件,则返回 RegLoadMUIStringW 函数中的错误代码。

(可选)若要指定要由 UAC 用户界面显示的应用程序图标,请添加以下注册表项:

HKEY_LOCAL_MACHINE\Software\Classes\CLSID
   {CLSID}
      Elevation
         IconReference = applicationIcon

IconReference 使用与 LocalizedString 相同的格式:

@pathtobinary-resourcenumber

此外,必须对 COM 组件进行签名才能显示图标。

COM 类还必须注释为已启用 LUA。 这需要以下注册表项:

HKEY_LOCAL_MACHINE\Software\Classes\CLSID
   {CLSID}
      Elevation
         Enabled = 1

如果缺少此项,激活将返回错误 CO_E_ELEVATION_DISABLED。

请注意,这些条目必须存在于 HKEY_LOCAL_MACHINE 配置单元中,而不是 HKEY_CURRENT_USER 或 HKEY_USERS 配置单元中。 这可以防止用户提升他们没有注册权限的 COM 类。

提升名字对象和提升 UI

如果客户端已提升,则使用提升名字对象不会显示提升 UI。

如何使用提升名字对象

提升名字对象是标准 COM 名字对象,类似于会话、分区或队列名字对象。 它将激活请求定向到具有指定提升级别的指定服务器。 要激活的 CLSID 显示在名字对象字符串中。

提升名字对象支持以下运行级别令牌:

  1. 管理员
  2. 最高

此配置的语法如下:

Elevation:Administrator!new:{guid}
Elevation:Highest!new:{guid}

前面的语法使用“new”名字对象返回 guid 指定的 COM 类的实例。 请注意,“new”名字对象在内部使用 IClassFactory 接口获取类对象,然后对其调用 IClassFactory::CreateInstance

提升名字对象还可用于获取实现 IClassFactory 的类对象。 然后调用方调用 CreateInstance 以获取对象实例。 此配置的语法如下:

Elevation:Administrator!clsid:{guid}

示例代码

以下代码示例显示如何使用提升名字对象。 它假定已在当前线程上初始化 COM。

HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, __out void ** ppv)
{
    BIND_OPTS3 bo;
    WCHAR  wszCLSID[50];
    WCHAR  wszMonikerName[300];

    StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[0])); 
    HRESULT hr = StringCchPrintf(wszMonikerName, sizeof(wszMonikerName)/sizeof(wszMonikerName[0]), L"Elevation:Administrator!new:%s", wszCLSID);
    if (FAILED(hr))
        return hr;
    memset(&bo, 0, sizeof(bo));
    bo.cbStruct = sizeof(bo);
    bo.hwnd = hwnd;
    bo.dwClassContext  = CLSCTX_LOCAL_SERVER;
    return CoGetObject(wszMonikerName, &bo, riid, ppv);
}

BIND_OPTS3 是 Windows Vista 中的新增功能。 它派生自 BIND_OPTS2

唯一的添加是 HWND 字段 hwnd。 此句柄表示一个窗口,该窗口将成为提升 UI 的所有者(如果适用)。

如果 hwndNULL,COM 将调用 GetActiveWindow 以查找与当前线程关联的窗口句柄。 如果客户端是脚本,则可能会出现这种情况,该脚本无法填充 BIND_OPTS3 结构。 在这种情况下,COM 将尝试使用与脚本线程关联的窗口。

过肩 (OTS) 提升

过肩 (OTS) 提升是指客户端使用管理员凭据(而不是自己的凭据)运行 COM 服务器的方案。 (“即时权限提升”一词是指管理员在客户端运行服务器时监视客户端。)

此方案可能会导致对服务器的 COM 调用出现问题,因为服务器可能不会显式(即以编程方式)或隐式(即声明方式使用注册表)调用 CoInitializeSecurity。 对于此类服务器,COM 会计算一个安全描述符,该描述符只允许 SELF、SYSTEM 和 Builtin\Administrators 对服务器进行 COM 调用。 这种安排在 OTS 方案中不起作用。 相反,服务器必须显式或隐式调用 CoInitializeSecurity,并指定包含 INTERACTIVE 组 SID 和 SYSTEM 的 ACL。

下面的代码示例演示如何使用 INTERACTIVE 组 SID 创建安全描述符 (SD)。

BOOL GetAccessPermissionsForLUAServer(SECURITY_DESCRIPTOR **ppSD)
{
// Local call permissions to IU, SY
    LPWSTR lpszSDDL = L"O:BAG:BAD:(A;;0x3;;;IU)(A;;0x3;;;SY)";
    SECURITY_DESCRIPTOR *pSD;
    *ppSD = NULL;

    if (ConvertStringSecurityDescriptorToSecurityDescriptorW(lpszSDDL, SDDL_REVISION_1, (PSECURITY_DESCRIPTOR *)&pSD, NULL))
    {
        *ppSD = pSD;
        return TRUE;
    }

    return FALSE;
}

下面的代码示例演示如何使用上一个代码示例中的 SD 隐式调用 CoInitializeSecurity

// hKey is the HKCR\AppID\{GUID} key
BOOL SetAccessPermissions(HKEY hkey, PSECURITY_DESCRIPTOR pSD)
{
    BOOL bResult = FALSE;
    DWORD dwLen = GetSecurityDescriptorLength(pSD);
    LONG lResult;
    lResult = RegSetValueExA(hkey, 
        "AccessPermission",
        0,
        REG_BINARY,
        (BYTE*)pSD,
        dwLen);
    if (lResult != ERROR_SUCCESS) goto done;
    bResult = TRUE;
done:
    return bResult;
}

下面的代码示例演示如何使用上述 SD 显式调用 CoInitializeSecurity

// Absolute SD values
PSECURITY_DESCRIPTOR pAbsSD = NULL;
DWORD AbsSdSize = 0;
PACL  pAbsAcl = NULL;
DWORD AbsAclSize = 0;
PACL  pAbsSacl = NULL;
DWORD AbsSaclSize = 0;
PSID  pAbsOwner = NULL;
DWORD AbsOwnerSize = 0;
PSID  pAbsGroup = NULL;
DWORD AbsGroupSize = 0;

MakeAbsoluteSD (
    pSD,
    pAbsSD,
    &AbsSdSize,
    pAbsAcl,
    &AbsAclSize,
    pAbsSacl,
    &AbsSaclSize,
    pAbsOwner,
    &AbsOwnerSize,
    pAbsGroup,
    &AbsGroupSize
);

if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
{
    pAbsSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LMEM_FIXED, AbsSdSize);
    pAbsAcl = (PACL)LocalAlloc(LMEM_FIXED, AbsAclSize);
    pAbsSacl = (PACL)LocalAlloc(LMEM_FIXED, AbsSaclSize);
    pAbsOwner = (PSID)LocalAlloc(LMEM_FIXED, AbsOwnerSize);
    pAbsGroup = (PSID)LocalAlloc(LMEM_FIXED, AbsGroupSize);

    if ( ! (pAbsSD && pAbsAcl && pAbsSacl && pAbsOwner && pAbsGroup))
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }
    if ( ! MakeAbsoluteSD(
        pSD,
        pAbsSD,
        &AbsSdSize,
        pAbsAcl,
        &AbsAclSize,
        pAbsSacl,
        &AbsSaclSize,
        pAbsOwner,
        &AbsOwnerSize,
        pAbsGroup,
        &AbsGroupSize
        ))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }
}
else
{
    hr = HRESULT_FROM_WIN32(GetLastError());
    goto Cleanup;
}

// Call CoInitializeSecurity .

COM 权限和强制访问标签

Windows Vista 在安全描述符中引入了强制访问标签概念。 该标签指示客户端是否可以获取对 COM 对象的执行访问权限。 该标签在安全描述符的系统访问控制列表 (SACL) 部分中指定。 在 Windows Vista 中,COM 支持 SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP 标签。 在 Windows Vista 之前,COM 权限中的 SCL 在操作系统上将被忽略。

从 Windows Vista 起,dcomcnfg.exe 不支持在 COM 权限中更改完整性级别 (IL)。 必须以编程方式设置。

下面的代码示例演示如何使用允许从所有 LOW IL 客户端启动/激活请求的标签创建 COM 安全描述符。 请注意,标签对于启动/激活和调用权限有效。 因此,可以编写不允许使用特定 IL 的客户端启动、激活或调用的 COM 服务器。 有关完整性级别的详细信息,请参阅了解保护模式和在保护模式下使用 Internet Explorer 中的“了解 Windows Vista 的完整性机制”部分。

BOOL GetLaunchActPermissionsWithIL (SECURITY_DESCRIPTOR **ppSD)
{
// Allow World Local Launch/Activation permissions. Label the SD for LOW IL Execute UP
    LPWSTR lpszSDDL = L"O:BAG:BAD:(A;;0xb;;;WD)S:(ML;;NX;;;LW)";
    if (ConvertStringSecurityDescriptorToSecurityDescriptorW(lpszSDDL, SDDL_REVISION_1, (PSECURITY_DESCRIPTOR *)&pSD, NULL))
    {
        *ppSD = pSD;
        return TRUE;
    }
}

BOOL SetLaunchActPermissions(HKEY hkey, PSECURITY_DESCRIPTOR pSD)
{
    
    BOOL bResult = FALSE;
    DWORD dwLen = GetSecurityDescriptorLength(pSD);
    LONG lResult;
    lResult = RegSetValueExA(hkey, 
        "LaunchPermission",
        0,
        REG_BINARY,
        (BYTE*)pSD,
        dwLen);
    if (lResult != ERROR_SUCCESS) goto done;
    bResult = TRUE;
done:
    return bResult;
};

CoCreateInstance 和完整性级别

CoCreateInstance 的行为在 Windows Vista 中已更改,以防止低 IL 客户端默认绑定到 COM 服务器。 服务器必须通过指定 SACL 显式允许此类绑定。 CoCreateInstance 的更改如下所示:

  1. 启动 COM 服务器进程时,服务器进程令牌中的 IL 设置为客户端或服务器令牌 IL,以较低者为准。
  2. 默认情况下,COM 将阻止低 IL 客户端绑定到任何 COM 服务器的运行实例。 若要允许绑定,COM 服务器的启动/激活安全描述符必须包含指定低 IL 标签的 SACL(请参阅上一部分,了解用于创建此类安全描述符的示例代码)。

提升的服务器和 ROT 注册

如果 COM 服务器希望在运行对象表 (ROT) 中注册并允许任何客户端访问注册,则必须使用 ROTFLAGS_ALLOWANYCLIENT 标志。 “激活即激活器”COM 服务器无法指定 ROTFLAGS_ALLOWANYCLIENT,因为 DCOM 服务控制管理器 (DCOMSCM) 对此标志强制实施欺骗检查。 因此,在 Windows Vista 中,COM 增加了对 ROTFlags 新注册表项的支持,允许服务器规定向任何客户端提供其 ROT 注册。

HKEY_LOCAL_MACHINE\Software\Classes\AppID
   {APPID}
      ROTFlags

此 REG_DWORD 条目的唯一有效值为:

ROTREGFLAGS_ALLOWANYCLIENT 0x1

该项必须存在于 HKEY_LOCAL_MACHINE 配置单元中。

此项为“激活即激活器”服务器提供的功能与 ROTFLAGS_ALLOWANYCLIENT为 RunAs 服务器提供的功能相同。

COM 中的安全性

Understanding and Working in Protected Mode Internet Explorer(了解和使用保护模式下的 Internet Explorer)