Windows 完整性机制设计

Windows 完整性机制是 Windows 安全体系结构的扩展,它基于内核中的安全参考监视器。 安全参考监视器通过将安全访问令牌中的用户和组 SID 与对象安全描述符的 ACL 中授予的访问权限进行比较来强制实施访问控制。 完整性机制将完整性级别添加到安全访问令牌,并向安全描述符中的系统 ACL (SACL) 添加强制标签访问控制项。

完整性机制的主要部分包括:

  • 预定义的完整性级别及其表示形式。
  • 限制访问权限的完整性策略。
  • 分配给安全访问令牌的完整性级别。
  • 强制标签访问控制项。
  • 分配给对象的必需标签。
  • AccessCheck 和内核模式 SeAccessCheck API 中的完整性限制。

下面更详细地描述了其中每个部分。 Windows 完整性机制始终有效,独立于其他安全策略选项。 完整性级别检查(如访问控制检查)不是可选的。 没有用于禁用完整性级别检查的安全策略选项。 尽管完整性机制支持 UAC,但当安全组策略禁用 UAC 时,完整性机制仍然有效。

完整性级别

Windows 使用 SID 定义完整性级别。 使用 SID 表示完整性级别,可以轻松地将完整性机制集成到现有安全数据结构中,而无需更改代码。 完整性级别 SID 具有以下形式:S-1-16-xxxx。 表 1 显示了完整性级别 SID 的组件。

表 1 完整性级别 SID 标识符颁发机构值

说明

16

表示强制标签颁发机构 (SECURITY_MANDATORY_LABEL_AUTHORITY) 。

Xxxx

表示完整性级别的 RID) 字段 (相对标识符。 RID 是表示完整性级别的十六进制值。

Windows Vista 中有四个主要完整性级别,具有四个相应的值。 较低的值表示较低的完整性级别或较低的可信度级别。 这些值在头文件 winnt.h 中定义。 表 2 显示了定义的完整性级别及其相应的值。

表 2 定义的完整性级别和相应的值

说明 符号

0x0000

不受信任的级别

SECURITY_MANDATORY_UNTRUSTED_RID

0x1000

完整性级别低

SECURITY_MANDATORY_LOW_RID

0x2000

中等完整性级别

SECURITY_MANDATORY_MEDIUM_RID

0x3000

高完整性级别

SECURITY_MANDATORY_HIGH_RID

0x4000

系统完整性级别

SECURITY_MANDATORY_SYSTEM_RID

中等完整性级别 SID 的一个示例是以下字符串:S-1-16-8192。 RID 值 8192 是0x2000的小数等效项。

RID 按0x1000间隔分隔,以便在将来定义其他级别。 分离还允许将完整性级别分配给略高于中等级别的进程:例如,为了满足特定的系统设计目标。

定义的完整性级别 SID 具有与之关联的字符串名称值。 使用 API LookupAccountSID 将返回每个完整性级别 SID 的字符串名称。 表 3 显示了完整性级别的字符串名称。

表 3 完整性级别字符串名称

完整性级别 SID 名称

S-1-16-4096

必需标签\低强制级别

S-1-16-8192

必需标签\中等强制级别

S-1-16-12288

必需标签\高强制级别

S-1-16-16384

必需标签\系统强制级别

完整性级别还定义为安全描述符定义语言 (SDDL) 中的 SID 字符串。 SDDL 定义 ConvertSecurityDescriptorToStringSecurityDescriptor 和 ConvertStringSecurityDescriptorToSecurityDescriptor 函数用来将安全描述符描述为文本字符串的字符串格式。 该语言还定义字符串元素,用于描述安全描述符组件中的信息。 使用 SDDL 定义完整性级别可以方便地在创建对象时定义对象的完整性级别。 有关完整性级别的 SID 字符串的详细信息,请参阅 Windows 完整性机制资源附录 A:强制标签的 SDDL 中介绍了对强制标签的 SDDL 支持。

Windows Vista 使用安全访问令牌中的完整性级别 SID 来表示主体完整性级别,并使用安全描述符中强制标签 ACE 中的完整性级别 SID 来表示对象完整性级别。

完整性策略

Windows 完整性机制使用简单的策略来确定如何在对象上使用强制标签来限制可供较低完整性主体访问的级别。 这意味着策略限制允许较低完整性级别对象访问完整性级别较高的对象的访问类型。 表 4 显示了两个策略类别。

表 4 完整性策略类别

类别 说明

访问令牌强制策略

在访问令牌中设置,并确定强制策略如何应用于访问令牌中表示的主题。

强制标签策略

在强制标签中设置 ACE (如下所述,) 对象,并确定如何限制对对象的访问。

在评估对安全对象的访问权限时执行强制策略检查时,将使用完整性策略。 策略确定如何跨完整性级别限制访问权限。

必需的访问令牌策略

表 5 显示了 Windows Vista 中定义的强制访问令牌策略。

表 5 Windows Vista 中的强制访问令牌策略

策略 描述

TOKEN_MANDATORY_NO_WRITE_UP

分配给所有访问令牌的默认策略。 该策略将此主体的写入访问权限限制为具有更高完整性级别的任何对象。

TOKEN_MANDATORY_NEW_PROCESS_MIN

控制将完整性级别分配给子进程的行为。 通常,当父进程访问令牌分配给子进程时,子进程会继承父进程的完整性级别。 使用NEW_PROCESS_MIN策略时,子进程的完整性级别将是父访问令牌的最低完整性级别或新进程的可执行文件的对象完整性级别。 默认情况下,此策略在所有访问令牌中设置。

可以通过示例描述NEW_PROCESS_MIN策略。 假设有一个名为 lowcalc.exe 的可执行程序。 为程序文件分配了一个低完整性标签。 在命令提示符下,用户lowcalc.exe运行。 父进程(cmd.exe)在中等完整性级别运行。 由于图像文件(lowcalc.exe)具有低完整性标签,因此NEW_PROCESS_MIN策略将新进程的完整性级别(lowcalc)设置为“低”。 下面设计 应用程序以在低完整性级别运行部分中的示例对此进行了说明。

强制标签策略

强制标签策略定义为在强制标签访问控制项的 “掩码 ”字段中 (位) 标志。 这些策略标志确定对适用于较低完整性主体的访问权限的限制。 表 6 显示了 Windows Vista 中定义的强制标签策略。

表 6 Windows Vista 中的强制标签策略

策略 描述

SYSTEM_MANDATORY_POLICY_NO_WRITE_UP

所有对象强制标签的默认策略。 标志等效于NO_WRITE_UP访问令牌策略。 该策略限制完整性级别较低的主题对 对象的写入访问。

SYSTEM_MANDATORY_POLICY_NO_READ_UP

限制完整性级别较低的主题对对象的读取访问权限。 例如,该策略用于限制对进程的虚拟内存地址空间的读取访问。

SYSTEM_MANDATORY_POLICY_NO_EXECUTE_UP

限制完整性级别较低的主体对 对象的执行访问。 例如,该策略用于限制较低完整性主体对 COM 类的启动激活权限。

这些定义的策略满足 Windows Vista 的设计目标。 完整性机制的体系结构允许未来扩展,方法是定义其他策略选项,用于控制不同完整性级别的主体和对象之间的访问权限。

如何将完整性策略映射到一般权限

强制策略检查作为 AccessCheck 安全功能的一部分发生。 ) 内核模式下的 AccessCheck (和 SeAccessCheck 作为GENERIC_MAPPING结构的函数参数之一。 调用方提供的泛型映射将特定于对象的访问权限映射到读取、写入和执行的泛型类别。 强制标签策略根据泛型类别对允许的访问实施限制。 对于特定对象类型,GENERIC_MAPPING与强制策略通信,检查禁止哪些特定访问权限。

如果传入 AccessCheck 调用的GENERIC_MAPPING结构都是零 (0) ,则泛型映射未定义。 未定义泛型映射时,强制策略检查限制完整性较低的主体的所有访问权限。 使用 AccessCheck 实现专用对象安全性并传递所有零的GENERIC_MAPPING结构的资源管理器将看到 “拒绝访问” 错误。 需要对资源管理器应用程序进行代码更改,以便将特定于对象类型的访问权限映射到强制策略强制实施的泛型访问权限。

如何为访问令牌分配完整性级别

安全访问令牌是内核使用的内部数据结构。 安全访问令牌包含对应于特权、组成员身份和其他安全相关详细信息的不同信息值。 当用户以交互方式登录到 Windows 或网络身份验证时,将发生访问令牌初始化。 初始化访问令牌时,除其他值外,还会添加用户的 SID、组 SID 和权限。 初始化访问令牌时,Windows Vista 会为访问令牌分配完整性级别。

内核将访问令牌分配给每个进程和线程。 进程的主访问令牌包含与该进程关联的完整性级别。 在 Windows 完整性机制中,进程的完整性级别称为主题完整性级别。 如果应用程序的访问令牌包含中等完整性 SID,则应用程序进程在中等完整性级别运行。 同一用户帐户下运行的多个应用程序进程将分配相同的主访问令牌。 这就是为每个应用程序分配相同完整性级别的方式。

根据TOKEN_GROUPS结构中存在的特定组 SID,将完整性级别分配给访问令牌。 Windows 内核根据特定的内置用户或组帐户分配完整性级别。 Windows Vista 分配具有本地系统帐户 SID 表示系统完整性级别的访问令牌,将具有本地管理员组 SID 存在的访问令牌分配为高完整性级别,为标准用户帐户分配访问令牌的完整性级别为中等。

表 7 显示基于特定 SID 的存在情况对访问令牌的完整性级别的分配。

表 7 链接到特定 SID 的完整性级别

访问令牌中的 SID 分配的完整性级别

LocalSystem

系统

LocalService

系统

NetworkService

系统

Administrators

备份操作员

Network Configuration Operators

Cryptographic Operators

经过身份验证的用户

中型

每个人 (世界)

匿名

不受信任

完整性级别为在不同访问级别上运行的应用程序定义了不同的可信度级别。 Windows Vista 中的大多数应用程序在中等完整性级别以标准用户访问级别运行。 中等完整性级别的应用程序在与其他应用程序以及中等完整性级别的数据的交互方式方面没有任何限制。 需要管理权限的特定任务或应用程序在高完整性级别运行。 系统服务在系统完整性级别运行,因为它们与默认桌面交互的能力存在限制,并且它们通常以强大的系统权限运行。 一些设计为以尽可能少权限运行的应用程序(如保护模式 Internet Explorer)可以以较低的完整性级别运行。

某些管理 Windows 权限只能分配给至少具有高完整性级别的访问令牌。 如果访问令牌完整性级别低于高,则不允许使用特定的管理权限,并且会从访问令牌中删除。 与高完整性级别关联的管理权限包括:

  • SE_CREATE_TOKEN_PRIVILEGE
  • SE_TCB_PRIVILEGE
  • SE_TAKE_OWNERSHIP_PRIVILEGE
  • SE_BACKUP_PRIVILEGE
  • SE_RESTORE_PRIVILEGE
  • SE_DEBUG_PRIVILEGE
  • SE_IMPERSONATE_PRIVILEGE
  • SE_RELABEL_PRIVILEGE
  • SE_LOAD_DRIVER_PRIVILEGE

创建子进程时,应用程序可以为重复访问令牌分配低完整性级别。 如果可执行程序映像文件具有低强制标签,则 Windows 可能会运行完整性级别较低的应用程序进程。 本文稍后将介绍这些功能。

如何获取访问令牌的完整性级别

完整性级别使用 TOKEN_GROUPS 字段存储在访问令牌中。 TOKEN_GROUPS结构是标识该用户帐户的组成员身份的 SID 和属性的列表。 访问令牌完整性级别也使用 SID 属性在组列表中标识。 SID_AND_ATTRIBUTES 结构包含完整性级别 SID,完整性级别是使用SE_GROUP_INTEGRITY和SE_GROUP_INTEGRITY_ENABLED属性标识的。

可以使用 GetTokenInformation API 从访问令牌检索访问令牌完整性级别。 GetTokenInformation 具有一个参数,用于指示要检索的访问令牌信息类。 TOKEN_INFORMATION_CLASS 参数具有为完整性级别 TokenIntegrityLevel 定义的值。 返回的数据结构是TOKEN_MANDATORY_LABEL类型。

确定进程的完整性级别

  1. 打开进程的访问令牌的句柄。

  2. 获取访问令牌的完整性级别。

  3. 将完整性级别 SID 与系统定义的完整性级别 RID 进行比较。

附录 D 中显示了用于获取访问令牌完整性级别的示例代码。

如何查看访问令牌的完整性级别

可以使用公开进程安全详细信息的工具(例如,SysInternals.com 中的进程资源管理器)查看进程的安全访问令牌中的完整性级别。 下图显示了“进程资源管理器”中的显示,其中“完整性级别”列添加到视图 (右侧) 。

图 1 进程资源管理器中的完整性级别

如果查看特定进程(例如explorer.exe)的安全属性,则进程资源管理器会在进程的安全访问令牌中定义的组列表中显示完整性级别。 下图显示了分配给进程的访问令牌的必需标签/中等强制级别,explorer.exe。

图 2 Explorer.exe作为中等完整性级别进程

如何设置访问令牌的完整性级别

应用程序通常不需要更改进程完整性级别的值。 应用程序通常不需要对安全访问令牌中的任何值进行更改。 但是,在某些情况下,支持不同完整性级别的本地客户端的服务可能会选择在较低的完整性级别执行任务。 模拟是服务线程可能以较低的完整性级别运行的一种方式。 当服务线程模拟本地客户端时,模拟线程具有客户端的安全上下文,其中包括客户端的完整性级别(如果客户端在同一本地计算机上运行)。

应用程序可以在较低完整性级别执行操作(例如创建一组文件和注册表项)的另一种方法是为当前线程访问令牌设置 TokenIntegrityLevel。 新的完整性级别不能高于进程的主要访问令牌中的完整性级别。

在线程上设置访问令牌完整性级别的值

  1. 调用 OpenThreadToken 以获取访问令牌的句柄。

  2. 使用 TokenIntegrityLevelTOKEN_INFORMATION_CLASS参数值调用 GetTokenInformation 以保存当前线程完整性级别。

  3. 使用 TokenIntegrityLevelTOKEN_INFORMATION_CLASS参数值调用 SetTokenInformation

由于不同完整性级别的线程之间的进程地址空间中没有安全边界,因此应用程序设计人员必须考虑在线程中以较低完整性运行的代码在较高完整性进程中运行的潜在安全风险。 大多数应用程序可能会发现创建在较低完整性级别运行的单独进程以执行较低完整性任务更简单。

如何设置访问令牌的完整性级别中显示了如何设置 TokenIntegrityLevel 并在较低完整性级别创建进程的示例。

强制标签 ACE

Windows 完整性机制定义了新的 ACE 类型,即系统强制标签 ACE。 必需标签 ACE 用于表示对象的安全描述符中对象的必需标签。 强制标签包含完整性级别和关联的完整性策略。 强制标签 ACE 仅在安全描述符的系统 ACL 或 SACL 中使用。 SACL 是安全描述符中与任意 ACL (DACL) 的单独字段。

图 3 安全描述符字段

可自由支配的 ACL 包含对 对象的用户和组访问权限。 任何用户或组都可以使用WRITE_DAC对象访问权限对 DACL 进行更新。 可自由选择的 ACL 可以更频繁地由不同的用户更新。 完整性机制的设计目标之一是,在对对象应用强制标签后,安全系统应使用 对象维护标签。 在对象安全描述符中强制实施相应的必需标签,对旨在管理 ACL 的应用程序影响不大或没有影响,方法是将强制标签置于系统 ACL 中,而系统 ACL 主要受安全系统的控制。 强制标签在逻辑上独立于 SACL 中的系统审核条目。 强制标签在 SACL 中的系统审核条目之前或之后没有必需的顺序。

强制标签 ACE 类似于允许访问的 ACE,因为它包含 ACE 标头、访问掩码和 SID。 强制标签 ACE 的 SID 部分包含完整性级别 SID。 表 8 显示了 ACE 标头中的字段。

表 8 ACE 标头中包含的字段

ACE 标头字段

AceType

SYSTEM_MANDATORY_LABEL_ACE_TYPE

AceFlags

定义强制标签 ACE 类型的继承的控制标志

AceSize

必需标签 ACE 的大小

强制标签 ACE 包含 掩码 字段。 掩码用于定义强制策略,这些策略确定对适用于进程 (或具有较低完整性级别的主题) 的访问权限的限制。 Windows Vista 中广泛使用的一个示例策略是NO_WRITE_UP策略。 强制标签 ACE 掩码中的NO_WRITE_UP策略标志意味着访问令牌中完整性级别 () 低于对象上强制标签中完整性级别 SID 的使用者将受到限制,无法获取对象的通用写入访问权限。

对象上只有一个有效的强制标签。 如果安全描述符恰好在 SACL 中获取多个必需标签,则 SACL 中的第一个必需标签 ACE 是对象的有效标签。

有关强制标签 ACE 的详细信息,请参阅 Windows 完整性机制资源

读取必需标签 ACE

Windows Vista 使用安全功能 GetSecurityInfo 从对象读取必需的标签信息。 GetSecurityInfo 从对象获取所有者、组、DACL 或 SACL 信息。 GetSecurityInfo 的 SECURITY_INFORMATION 参数确定返回安全描述符的哪个部分。 Windows Vista 定义了一个新的安全信息值(LABEL_SECURITY_INFORMATION),该值用于从 SACL 返回强制标签 ACE。 如果对 GetSecurityInfo 的调用指定了SACL_SECURITY_INFORMATION,则仅返回 SACL 中的系统审核 ACE,而不返回强制标签 ACE。

读取对象上强制标签 ACE 所需的访问权限是READ_CONTROL标准访问权限。 通常,需要ACCESS_SYSTEM_SECURITY访问权限才能在 SACL 中获取或设置信息。 仅当访问令牌中启用了SE_SECURITY_NAME特权时,才会授予ACCESS_SYSTEM_SECURITY。 从 SACL 读取强制标签不需要SE_SECURITY_NAME权限或ACCESS_SYSTEM_SECURITY访问权限。

设置强制标签 ACE

Windows Vista 使用安全功能 SetSecurityInfo 更改对象上的必需标签信息。 对象的必需标签在创建对象时由安全子系统设置。 SetSecurityInfo 的 SECURITY_INFORMATION 参数必须包含LABEL_SECURITY_INFORMATION才能更改安全描述符中的强制标签。

创建对象时分配的必需标签通常是对象的正确完整性级别,无需更改强制标签。 但是,当应用程序设计为支持不同完整性级别的多个客户端进程时,可能存在更改对象完整性级别的应用程序设计要求。

更改安全描述符 SACL 中强制标签 ACE 所需的权限WRITE_OWNER。 将强制标签写入 SACL 不需要SE_SECURITY_NAME权限或ACCESS_SYSTEM_SECURITY访问权限。 强制标签中的完整性级别可以设置为小于或等于使用者完整性级别的值。

最常见的更改是降低对象的完整性级别,以允许较低完整性的应用程序进程具有修改访问权限。 例如,中等完整性级别的应用程序需要与在低完整性级别运行的另一个应用程序进程同步。 中等完整性进程创建一个命名互斥对象,并为其分配一个低级别的必需标签,以允许较低完整性进程打开互斥体以向事件发出信号。

重新标记权限

Windows 完整性机制定义了新的 Windows 安全特权,SE_RELABEL_NAME。 在安全访问令牌中启用时,重新标记安全特权允许使用者将对象的必需标签设置为比主体访问令牌中的完整性级别更高的完整性级别。 Windows Vista 的默认安全策略不会将此权限分配给任何用户或组。 该特权旨在解决在高完整性级别运行的管理员进程需要在系统完整性级别更改或分配对象的必需标签的情况。 Windows Vista 没有任何需要或使用重新标记权限的任务。

Windows 如何将必需标签分配给对象

创建对象安全描述符时,Windows 会将强制标签分配给安全对象。 通过以下三种方式之一分配新对象的完整性级别:

  • 为对象创建安全描述符时,安全子系统会为对象分配一个强制标签。
  • 使用函数(如 CreateFile)创建对象时,创建过程将显式强制标签指定为安全属性。
  • 父容器定义可继承的强制标签 ACE,该标签适用于在容器中创建的子对象。

若要了解如何分配对象完整性级别,最简单的方法是假设为每个对象分配了一个必需标签,其完整性级别与创建进程 (或线程) 的主题完整性级别相同。 但是,根据对象类型和主题完整性级别,一般概念也有例外。 例外情况是设计选择的结果,在启用或禁用各种安全策略(如 UAC)时,这些设计选项对于支持其他系统要求以实现一致的用户体验是必需的。

强制标签可以应用于具有基于安全描述符的访问控制的所有安全对象。 有许多类型的安全对象,包括进程和线程对象、命名对象和永久性对象(如文件或注册表项)。 创建对象时,Windows Vista 不会为所有对象类型分配显式强制标签。 安全子系统在创建对象时向其分配强制标签的对象类型包括:

  • 进程
  • 线程
  • 访问令牌
  • 作业

所有其他对象类型都具有隐式默认值或继承的强制标签。 回想一下,在访问令牌初始化期间,会将完整性级别分配给在交互式登录期间创建的安全访问令牌。 代表用户登录启动的第一个进程是userinit.exe,它将启动 shell 进程,explorer.exe。 Userinit.exe由 Winlogon) (系统服务启动,该服务使用对 CreateProcessAsUser 的调用,并为交互式登录用户传递访问令牌。

CreateProcessAsUser 创建进程对象和初始线程等。 创建进程对象时,会将该进程的安全描述符从作为主要访问令牌分配给新进程的访问令牌中分配完整性级别。 当userinit.exe调用 CreateProcess 来启动 shell 时,将初始化 explorer.exe 的进程对象。 进程对象包括安全描述符和主访问令牌。 explorer.exe进程中的必需标签设置为创建过程的完整性级别,userinit.exe中等。 explorer.exe的主要访问令牌继承自创建父进程,userinit.exe,其完整性级别为中等。 当explorer.exe进程创建新线程时,会为线程对象提供一个安全描述符,安全子系统根据创建进程的完整性级别为线程对象分配一个完整性级别。 explorer.exe进程中的线程对象被分配为介质的完整性级别,这是创建进程的主要访问令牌的完整性级别。

注意

访问令牌对象是具有其自身安全描述符的安全对象。 令牌的安全描述符用于确定 OpenProcessToken 或 OpenThreadToken 函数期间允许的访问。 访问令牌对象的安全描述符中具有访问令牌对象上的必需标签。 访问令牌在访问令牌组列表中还包含一个表示使用者完整性级别的完整性级别 SID。

通过始终向进程、线程、令牌和作业对象分配强制标签,完整性机制可防止处于较低完整性级别的同一用户的进程访问这些对象类型并修改其内容或行为,例如注入 DLL 或模拟更高级别的访问令牌。

默认完整性级别

并非所有对象类型都会在安全描述符中分配强制标签 ACE。 如果安全描述符中存在强制标签 ACE,则称为显式强制标签。 如果没有必需的标签 ACE,则安全子系统在强制策略检查期间为该对象使用隐式默认强制标签。 默认强制标签为所有安全对象分配中等完整性级别。 如果未在安全描述符中定义强制标签,则隐式默认强制标签适用于所有对象类型。

中等的默认对象完整性级别与 NO_WRITE_UP 的默认强制策略相结合,通过主题完整性级别低于中等的进程来限制对所有对象的修改访问。 默认的强制标签和策略可防止不受信任的低完整性进程修改系统上可能允许 DACL 中任意写入访问的任何用户、系统文件或注册表项。

创建 NTFS 文件系统对象和注册表项时不会自动标记它们。 从以前版本的 Windows 升级到 Windows Vista 后,这些对象没有强制标签。 没有安全描述符 (CDFS 或 FAT32) 的非 NTFS 文件系统上的文件不是安全对象,也没有完整性级别。 每个安全描述符都必须具有隐式强制标签。

具有中等或更高主题完整性级别的进程会创建文件和注册表项,而无需显式标签。 因此,由高级别或系统完整性级别进程创建的文件系统和注册表对象具有隐式媒体标签。 使用强制标签的应用程序可以在创建对象时定义显式标签。 但是,默认情况下,Windows Vista 不会将高于中等完整性级别的标签分配给文件系统或注册表。 这并不意味着这些对象必须接受较低完整性进程的修改。 继承 (或默认) 文件系统或注册表对象上由高级或系统级别进程创建的任意访问控制列表,仅向管理员组的成员或本地系统或服务帐户授予写入访问权限。

使用媒体的默认隐式强制标签所需的许多设计约束,而不是根据主体的大多数对象类型的完整性级别分配显式强制标签。 一个特定示例基于使用本地安全策略启用或禁用用户帐户控制的能力。 禁用 UAC 时,作为本地管理员组成员的用户具有以高完整性级别使用完全特权访问令牌运行的所有进程。 如果在使用者的完整性级别显式标记所有对象,则将为用户创建的所有文件(如文档和电子表格)分配一个较高的完整性级别。 高标签似乎合适,即使用户配置文件的继承 DACL 权限为用户访问提供了足够的访问控制。 但是,如果本地计算机或组策略启用了 UAC,则同一用户运行的大多数进程都会在中等完整性级别分配经过筛选的安全访问令牌。 启用 UAC 后,用户将无法打开禁用 UAC 时创建的文件。 尝试打开用户高完整性文档的中等完整性应用程序将收到“拒绝访问”错误。

如果进程运行的主题完整性级别低于默认完整性级别的媒体,则进程将对具有隐式中等完整性级别的所有对象具有限制的访问权限。 具有低完整性级别的进程将无法修改任何显式或隐式完整性级别为中等或更高的对象,而不管 DACL 中授予了安全主体的访问权限。 因此,由主题完整性级别低于默认级别 (中等) 的进程创建的所有对象都由安全子系统显式标记。 Windows Vista 中可能存在对低完整性进程的访问限制,因为所有应用程序通常都在中等完整性级别运行,并且应用程序兼容性问题最少。 以低完整性正确运行的应用程序通常需要特定的设计更改才能在受限访问的情况下正常工作。 以下将应用程序 设计为在低完整性级别运行时的部分讨论了设计更改。

标记低主体创建的对象

可以在低完整性级别使用 CreateProcessAsUser 函数启动应用程序。 低进程由 CreateProcessAsUser 使用以下完整性级别信息进行初始化:

  • 新进程对象是使用包含低完整性的必需标签的安全描述符创建的。
  • 为该进程创建一个新的线程对象,并使用包含具有低完整性的必需标签的安全描述符。
  • 将主访问令牌分配给新进程。 访问令牌对象具有一个安全描述符,该安全描述符的必需标签为低,并且访问令牌包含一个 TokenIntegrityLevel,该令牌具有一个代表主题完整性级别的低完整性 SID。

假设低完整性进程正在运行,并且它想要创建线程。 若要创建线程,它必须打开自己的进程对象,以便具有写入访问权限才能创建新的线程对象。 如果进程对象上的必需标签为中等,低主题将无法打开进程,并且无法创建新线程。 由于进程对象也标记为低完整性,因此允许低主题打开进程进行写入访问并创建新的线程对象。 该示例说明了为什么进程对象、线程对象和安全访问令牌上的强制标签在同一完整性级别上保持一致非常重要。

注意

这适用于所有完整性级别,而不仅仅是低级主题。

假设低进程创建一个临时文件。 应用程序调用 CreateFile 以创建新文件、写入一些数据并关闭该文件。 稍后,低进程希望重新打开同一文件以追加数据。 如果临时文件在中等完整性级别具有隐式默认强制标签,则低级别进程将无法重新打开之前为修改访问而创建的文件。 对于完整性级别低于默认媒体级别的主题创建的任何安全对象,都可能出现相同的问题。 因此,系统会自动为完整性级别低于默认级别的使用者创建的所有安全对象分配显式强制标签。 换句话说,当主题完整性级别较低时,所有内核对象、注册表项和文件系统对象都显式标记为低。

如何创建具有特定必需标签的对象

大多数 Windows 应用程序不需要“完整性感知”。 安全子系统会自动创建对象并为其分配一个强制标签。 由于大多数应用程序在中等完整性级别运行,并且默认对象完整性级别为中等,因此完整性策略不会更改大多数应用程序允许的访问控制。 但是,某些应用程序(如服务)旨在支持不同完整性级别的客户端应用程序。 该服务可能以比客户端更高的完整性级别运行,并且可能需要在较低的完整性级别显式标记代表客户端创建的新对象。 对象的完整性级别可以通过创建过程进行设置。 约束是新对象上的完整性级别必须小于或等于创建过程的完整性级别。

创建具有特定必需标签的对象

  1. 创建定义低强制标签的 SDDL 安全描述符,例如:
    #define LOW_INTEGRITY_SDDL_SACL_W L"S:(ML;;NW;;;LW)"
  2. 使用 ConvertStringSecurityDescriptorToSecurityDescriptor 将 SDDL 字符串转换为安全描述符。
  3. 将具有低强制标签的安全描述符分配给安全属性结构。
  4. 将安全特性参数传递给调用以创建对象,例如 CreateFile。

强制标签继承

Windows Vista 不会在 NTFS 文件系统中显式标记文件和目录。 如前所述,安全子系统对安全描述符中没有强制标签的对象使用具有默认媒体级别的隐式强制标签。

应用程序可以设计为在低完整性级别运行。 受保护的模式 Internet Explorer 是 Windows Vista 应用程序的一个示例,该应用程序旨在以低完整性运行。 完整性较低的应用程序可能需要文件系统中的文件夹,他们可以在其中写入临时文件或应用程序数据。 对于受保护的模式 Internet Explorer,将使用用户配置文件中的“临时 Internet 文件”文件夹。 低应用程序如何写入文件系统文件夹? 必须为文件夹分配一个显式强制标签,该标签允许从低完整性进程进行写入访问。

根据应用程序设计,可能存在其他协作进程将文件添加到低完整性应用程序使用的文件夹。 如果其他进程也以低完整性运行,则会自动为这些进程创建的文件分配低强制标签。 但是,如果其他合作伙伴进程具有较高的完整性,则合作伙伴进程 (文件同步服务,或者用户代理) 可能会在低文件夹中创建未自动标记为低的文件。 合作伙伴进程在低应用程序使用的文件夹中创建一个中等文件,低级应用程序无法修改或删除该文件。

完整性机制允许具有低强制标签的文件夹具有子对象、文件和子文件夹,每个子对象具有不同的更高强制标签,因为每个文件系统对象都有唯一的安全描述符。 在许多情况下,其意图是,对于分配了低强制标签的文件夹,必须为该文件夹中的所有文件分配一个低强制标签,以便低进程可以修改它们。 完整性机制通过允许强制标签 ACE 从父容器继承到子容器和子对象来支持这一点。

必需标签 ACE 类型数据结构包含 AceFlags 字段的 ACE 标头。 AceFlags 字段用于定义与其他 ACE 类型的继承标志相同的 ACE 类型的继承标志,例如用于任意访问的允许访问 ACE 类型。 对于 容器继承 (CI) 和/或 对象继承 (OI) ,可以将强制标签 ACE 定义为可继承。 强制标签 ACE 不能 inherit_only (IO) 。 如果向容器分配了可继承的必需标签 ACE,则强制标签将应用于容器本身以及应用继承的子对象。

可继承的必需标签的一个示例是在每个用户配置文件下创建的文件夹之一上的低强制标签:%USERPROFILE%\AppData\LocalLow。 初始化配置文件时,此文件夹将分配一个低强制标签,并用作默认可由低完整性应用程序写入的顶级文件夹。 下图显示了使用 Icacls 命令显示的 AppData\LocalLow 文件夹上的可继承强制标签。 有关 Icacls 如何支持强制标签的详细信息,请参阅 附录 B:Icacls 和文件完整性级别

强制标签中的继承标志指示 ACE (OI) ,并且使用NO_WRITE_UP (NW) 强制策略 (CI) ACE。 在 AppData\LocalLow 文件夹下创建的所有子文件夹都将继承可继承的标签。

图 4 可继承的低强制标签

从中等进程创建新文件或子文件夹时,新对象将继承低强制标签。 在以下示例中,命令提示符以中等完整性级别在进程中运行。 在 LocalLow 文件夹中创建一个文件(newfile.txt)和一个新文件夹 Temp,并且这两个对象都从父容器继承低强制标签。 Icacls 指示强制标签继承 (I) 指定。

图 5 继承子对象上的强制标签

强制标签的继承可确保在文件系统命名空间的一部分下创建的对象的完整性级别保持一致。

继承和显式标签

在具有可继承强制标签的容器中创建的对象从容器继承强制标签。 但是,创建新对象(如文件)的过程可以为 create 函数提供安全特性参数中的显式强制标签,例如 CreateFile。

如果创建过程向对象 create 函数提供显式标签作为参数,则会为新对象分配调用方提供的显式强制标签,并且不会继承自父容器。 显式强制标签的完整性级别不高于创建对象的主题进程。 使用者提供的显式强制标签中的完整性级别可能高于父容器的可继承强制标签中的完整性级别。

仅继承限制

仅继承 (IO) 是访问控制项中的一个标志,指示 ACE 不控制对其所附加到对象的访问。 仅继承 ACE 通常分配给容器对象。 仅限继承的 ACE 对容器本身无效;相反,它适用于容器的子对象。 对于在新对象上设置INHERIT_ONLY标签时,完整性级别低于默认设置的主题存在限制。 如果为新容器对象传入的显式标签是级别低于默认值的INHERIT_ONLY标签,则该标签被视为无效且被忽略。 此限制的理由是,低主体创建容器,并尝试将容器上的INHERIT_ONLY标签设置为低。 如果接受INHERIT_ONLY标签,容器的有效完整性级别将是介质的隐式默认级别。

此外,使用者无法定义完整性级别高于使用者完整性级别的INHERIT_ONLY标签。 例如,中等主题无法定义完整性级别较高的INHERIT_ONLY标签。

受保护的 SACL 和标签继承

SDDL 支持定义受保护的 SACL,其中可能包括显式强制标签。 受保护的 SACL 在安全描述符的“SECURITY_DESCRIPTOR_CONTROL”字段中设置SE_SACL_PROTECTED标志。 与访问权限相关的LABEL_SECURITY_INFORMATION和SACL_SECURITY_INFORMATION的逻辑分离对于受保护的 SACL 的行为方式还不完整。 强制标签存储在 SACL 中。 受保护的 SACL 意味着强制标签也受到保护。

如果在SECURITY_DESCRIPTOR_CONTROL中设置了SE_SACL_PROTECTED标志,则强制标签也会受到保护。

  • 如果受保护的 SACL 中没有强制标签 ACE,则不会应用容器中可继承的强制标签 ACE, (新对象具有隐式默认强制标签) 。
  • 如果受保护的 SACL 中存在强制标签 ACE,则对容器上的可继承标签 ACE 的更改不会影响此对象。

有关 SDDL 的详细信息,请参阅 附录 A:用于强制标签的 SDDLWindows 完整性机制资源

访问检查如何与强制策略配合使用

AccessCheck 函数强制实施强制策略。 AccessCheck 将指定的安全描述符与指定的访问令牌进行比较,并在 AccessStatus 参数中指示是授予访问权限还是拒绝访问。 AccessCheck 函数首先将 ClientToken 中的完整性级别与 pSecurityDescriptor 的 SACL 中的强制标签进行比较,以确定哪些访问权限不可用,具体取决于强制策略。 在强制策略检查后,AccessCheck 会将所需的访问权限与 DACL 中授予的访问权限进行比较。

默认的强制策略可防止完整性较低的进程获得对高完整性对象的泛型写入访问权限。 例如,默认情况下,低完整性进程拒绝对具有较高完整性级别的对象的泛型写入访问权限。 如果 pSecurityDescriptor 不包含必需的 ACE,则假定为对象分配中等完整性级别的隐式必需 ACE。 GenericMapping 参数确定与泛型写入访问权限关联的访问权限。 如果 GenericMapping 参数全部为零,则完整性检查不会授予对完整性较低的 ClientToken 的任何特定访问权限。 在完整性检查根据强制策略确定调用方可用的通用访问权限后,安全描述符的 DACL 与 ClientToken 进行比较,以确定剩余授予的访问权限。

用户界面特权隔离 (UIPI) 和完整性

用户界面特权隔离 (UIPI) 在 Windows 子系统中实现限制,以防止低特权应用程序发送窗口消息或在更高特权进程中安装挂钩。 允许高特权应用程序将窗口消息发送到低特权进程。 这些限制在 SendMessage 和相关窗口消息函数中实现。 并非所有从较低特权进程发送到较高特权进程的窗口消息都会被阻止。 通常,“已读”类型消息(例如 WM_GETTEXT)可以从较低特权窗口发送到高特权窗口。 但是,写入类型消息(如WM_SETTEXT)会被阻止。

UI 特权级别位于进程级别,适用于进程创建的所有窗口。 当进程 (GDI) 对 Windows 图形设备接口进行首次调用时,USER 子系统将初始化。 在进程初始化期间,USER 子系统调用 安全子系统,以确定在进程的主要安全访问令牌中分配的完整性级别。 在进程初始化期间,USER 子系统设置 UI 特权级别后,它不会更改。 将线程的 TokenIntegrityLevel 设置为较低的值不会影响进程的 UI 特权级别,也不会影响该进程或线程打开的任何窗口。

UIPI 不会干扰或更改具有相同特权 (或完整性) 级别的应用程序之间的窗口消息传送行为。 UIPI 通过阻止以下行为来阻止低特权进程访问更高特权的进程。 特权较低的进程不能:

  • 对运行具有更高权限的进程执行窗口句柄验证。
  • 将 SendMessage 或 PostMessage 用于运行具有更高权限的应用程序窗口。 这些 API 返回成功,但以无提示方式删除窗口消息。
  • 使用线程挂钩附加到运行具有更高权限的进程。
  • 使用日志挂钩监视运行具有更高权限的进程。
  • 将动态链接库 (DLL) 注入到具有更高权限的进程。

启用 UIPI 后,仍会在不同特权级别的进程之间共享以下共享 USER 资源。

  • 实际上拥有屏幕表面的桌面窗口
  • 桌面堆只读共享内存
  • 全局 atom 表
  • 剪贴板

在屏幕上绘制是 UIPI 未阻止的另一个操作。 (GDI) 模型的 USER/图形设备界面不允许控制绘制图面。 因此,对于具有较高权限的应用程序,运行具有较少权限的应用程序可以在应用程序窗口表面进行绘制。

UI 自动化应用程序的 UIAccess

Microsoft UI 自动化是 Windows Vista 模型,用于支持辅助功能要求,并改进了早期模型(称为 Microsoft Active Accessibility (MSAA) )。 旨在支持可访问用户体验的应用程序代表用户控制其他 Windows 应用程序的行为。 当所有应用程序 (自动化客户端和服务器) 以标准用户身份运行时,即在中等完整性级别,UIPI 限制不会干扰 UI 自动化模型。

但是,有时管理用户可能以提升的权限在管理员审批模式下运行基于 UAC 的应用程序。 如果无法绕过 UIPI 实现的限制,UI 自动化程序将无法在桌面上驱动提升的应用程序的图形 UI。 UI 自动化程序可以使用程序的应用程序清单中的特殊安全属性(称为 UIAccess)来跨特权级别绕过 SendMessage 上的 UIPI 限制。

下面是 UIAccess 程序的应用程序清单条目的示例。

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> 
  <security> 
    <requestedPrivileges> 
    <requestedExecutionLevel 
      level="asInvoker" 
      UIAccess="true" /> 
    </requestedPrivileges> 
  </security> 
</trustInfo>

通过在 requestedPrivileges 属性中指定 UIAccess=“true” ,应用程序需要绕过跨特权级别发送窗口消息的 UIPI 限制。 Windows Vista 在使用 UIAccess 特权启动应用程序之前实现以下策略检查。

  • 应用程序必须具有一个数字签名,该签名可以使用数字证书进行验证,该证书链接到本地计算机受信任的根证书颁发机构证书存储中的受信任根。
  • 应用程序必须安装在只能由管理员写入的本地文件夹应用程序目录中,例如 Program Files 目录。 UI 自动化应用程序的允许目录为:
    • %ProgramFiles% 及其子目录。
    • %Windir% 及其子目录,其中几个子目录除外,因为标准用户具有写权限。

被排除的子目录的 %WinDir% 子目录包括:

  • \调试
  • \PCHealth
  • \注册
  • \System32\ccm
  • \System32\com
  • \System32\FxsTmp
  • \System32\Spool
  • \System32\Tasks

Windows Vista 为本地计算机策略和 组策略 提供安全设置,以调整必须从安全位置启动 UIAccess 程序的要求。 设置在“本地安全策略”下的“安全选项”下定义。 安全策略如下:

用户帐户控制: 仅提升安装在安全位置的 UIAccess 应用程序

默认情况下,此设置处于启用状态。 但是,如果必须从文件夹启动的 UIAccess 应用程序 (辅助技术) 不受仅限管理员的写入访问权限的保护,管理员可以禁用它。

如果请求 UIAccess 的 UI 自动化应用程序满足 UIAccess 设置要求,则 Windows Vista 将启动该应用程序,并能够绕过大多数 UIPI 限制。 如果 UI 自动化应用程序不符合安全限制,则应用程序将在没有 UIAccess 权限的情况下启动,并且只能与相同或较低特权级别的应用程序交互。

使用 UIAccess 权限启动的进程:

  • 能够设置前台窗口。
  • 可以使用 SendInput 函数驱动任何应用程序窗口。
  • 具有使用低级别挂钩、原始输入、GetKeyState、GetAsyncKeyState 和 GetKeyboardInput 的所有完整性级别的读取输入。
  • 可以设置日记帐挂钩。
  • 使用 AttachThreadInput 将线程附加到更高的完整性输入队列

使用标准用户的 UIAccess 权限启动的应用程序将在访问令牌中分配稍高一点的完整性级别值。 标准用户的 UIAccess 应用程序的访问令牌完整性级别是中等完整性级别的值,加上0x10增量。 UIAccess 应用程序的完整性级别较高会阻止同一桌面上处于中等完整性级别的其他进程打开 UIAccess 进程对象。