Windows-classic-samples 之 ResourceAttributes 示例:CAP 与资源属性安全描述符构建及 AuthzAccessCheck 实战

原创2026-10-14 21:39:091,379 阅读
文章标签:示例工程

Windows-classic-samples 之 ResourceAttributes 示例:CAP 与资源属性安全描述符构建及 AuthzAccessCheck 实战

本篇技术指南聚焦 Windows-classic-samples 仓库中的 ResourceAttributes 示例(README.md)。该示例是一个命令行工具,演示如何将一个集中访问策略(Central Access Policy,CAP)ID ACE 与一个或多个资源属性(Resource Attribute)ACE 写入安全描述符(Security Descriptor),再通过 AuthzAccessCheck 对该安全描述符执行访问检查并输出最终授予的访问权限掩码。读完本文,你将掌握 CAP 与资源属性在 Windows 安全模型中的协作方式、(Name, ValueType, Flags, Value1, Value2...) 资源属性的完整命令行语法,以及从安全描述符构建到授权判定的整条调用链。

示例概述:它能做什么

资源属性(Resource Attribute)与集中访问策略是 Windows Server 2012 R2 / Windows 8.1 引入的动态访问控制(Dynamic Access Control,DAC)能力的核心组成部分。本示例把两者结合起来:

  1. 将一个 CAP ID 以 Scoped Policy ID 形式写入安全描述符的 SACL;
  2. 将一个或多个资源属性以资源属性 ACE((RA;...))形式写入 SACL;
  3. 基于该安全描述符调用 AuthzAccessCheck,把判定结果(Access Mask)输出到控制台。

README 中特别说明:该示例此前曾被命名为 "Resource attributes sample",即它最初只演示资源属性,后来扩展为同时覆盖集中访问策略与资源属性两部分。

示例源码位于 cpp/ResourceAttributesSample.cpp,头文件与函数声明位于 cpp/ResourceAttributesSample.h,工程文件为 cpp/ResourceAttributesSample.vcxproj 与 cpp/ResourceAttributesSample.sln。

操作系统与运行环境要求

README 明确列出的系统要求:

  • 客户端:Windows 8.1
  • 服务器:Windows Server 2012 R2

除操作系统版本外,还有更重要的环境前提(README「Run the sample」一节明确强调):

  • 要在 Active Directory 林中预先定义好声明类型(Claim Types)与集中访问策略(Central Access Policies);
  • 要测试的 CAP 必须已经应用到运行该工具的这台机器上(即成为该机器当前生效的集中访问策略之一)。

若缺少上述前提,涉及 CAP ID 或资源声明的参数将无法正确执行——IsValidCapID 会通过 LsaGetAppliedCAPIDs 检查 CAP 是否已应用,未应用时直接列出当前机器上已应用的全部 CAP ID 并退出(详见下文「源码剖析」)。

构建示例

README 给出的构建步骤:

  1. 使用 Visual Studio 2013 for Windows 8.1(任意 SKU)或更新版本的 Visual Studio 打开 ResourceAttributesSample.sln;
  2. 加载完成后按 F7(Visual Studio 2013 中也可按 F6),或通过菜单 Build → Build Solution 完成构建。

从工程文件 cpp/ResourceAttributesSample.vcxproj 可以看到工程本身的细节:

  • 目标为 控制台应用程序(<SubSystem>Console</SubSystem>),生成 ResourceAttributesSample.exe;
  • 字符集为 Unicode(<CharacterSet>Unicode</CharacterSet>);
  • 链接依赖库为 advapi32.lib、ntdll.lib、authz.lib、kernel32.lib——其中 authz.lib 对应授权检查 API,advapi32.lib 提供安全描述符与 ACL 相关 API;
  • 工程元数据中声明了 PlatformToolset 为 v110,配置为 Debug|Win32 与 Release|Win32 两组。如 README 所述,示例面向 Windows 8.1 / Windows Server 2012 R2 编写,多数情况下可原样在新版本 Visual Studio 中编译运行。

运行示例与命令行语法

构建完成后,按 F5(带调试运行)或 Ctrl-F5(不带调试运行)即可执行;也可在 Debug 菜单中选择对应选项。

不携带任何命令行参数运行时,程序会输出工具的使用说明(对应源码中 wmain 对 argc != 5 的校验与 PrintUsage() 调用)。完整用法如下(摘自 ResourceAttributesSample.cpp 中 PrintUsage,L1427-L1460):

ResourceAttributesSample [-cap <ID>] [-file <PATH> | -ra <ATTRIBUTES>]

  -cap  : 集中访问策略 SID(CAP ID),格式为 S-1-17-...
  -file : 用于获取资源属性的文件路径,文件的资源属性会写入安全描述符,
          同时打印到控制台
  -ra   : 要写入安全描述符的资源属性,格式为
          (Name, ValueType, Flags, Value1, Value2...)

参数详细说明:

参数段 含义 取值
Name 资源属性名 字符串,如 Int、Confidentiality
ValueType 值类型 TI(Integer 有符号整数)、TU(Unsigned Integer 无符号整数)、TB(Boolean 布尔)、TS(String 字符串)
Flags 标志位 十六进制标志值,如 0x0
Value 属性值 逗号分隔的一个或多个值

官方示例命令(PrintUsage 中给出):

ResourceAttributesSample -cap S-1-17-11 -file C:\test.txt
ResourceAttributesSample -cap S-1-17-22 -ra ("Int",TI,0x0,123)

wmain 的参数校验逻辑(ResourceAttributesSample.cpp)保证了以下约束:

  • 总共需要 4 个参数(-cap、-file 或 -ra,各带一个值);
  • -cap 必须提供;
  • -file 与 -ra 必须二选一,不能同时出现,也不能都不出现;
  • 参数开关同时兼容 - 与 / 前缀(如 -file 与 /file),见头文件中的 FileParameter、CapParameter、RaParameter 三个内联函数。

示例的执行流程

源码文件头部的注释(L16-L35)清晰勾勒了程序的处理步骤,结合 wmain 的实际调用顺序可以还原完整流程:

  1. 读取并校验 CAP ID:调用 IsValidCapID 将 CAP ID 字符串转为 SID,并检查它是否已应用到本机;
    • 若已应用,则可在访问检查中使用;
    • 若未应用,则把本机已应用的 CAP 列表打印出来并退出。
  2. 获取资源属性(二选一):
    • 若提供了 -file,调用 GetFileResourceAttributes 读取文件的安全描述符,提取其中的资源属性 ACE,打印后用于构建安全描述符;若文件没有资源属性,则退出;
    • 若提供了 -ra,调用 ParseResourceAttributesArguments 解析命令行中的资源属性字符串。
  3. 构建安全描述符:调用 CreateSecurityDescriptor,生成包含 CAP ID ACE 与资源属性 ACE 的自相对(self-relative)安全描述符。
  4. 执行访问检查:调用 PerformAccessCheck,用当前进程令牌初始化 Authz 客户端上下文,对该安全描述符调用 AuthzAccessCheck,并把得到的授权结果(Access Mask)打印到控制台。

源码深度剖析:安全描述符的构建

CreateSecurityDescriptor(ResourceAttributesSample.cpp)是示例的核心,它按以下步骤组装安全描述符:

  1. InitializeSecurityDescriptor 初始化空的安全描述符;
  2. 通过 AllocateAndInitializeSid 创建 System SID(SECURITY_LOCAL_SYSTEM_RID)与 Everyone SID(SECURITY_WORLD_RID);
  3. SetSecurityDescriptorOwner / SetSecurityDescriptorGroup 把所有者与主组都设为 System;
  4. 在栈上 1 KB 的 DaclBuffer 中初始化 DACL,并用 AddAccessAllowedAce 添加一条授予 Everyone 完全控制(KEY_ALL_ACCESS)的允许 ACE,再 SetSecurityDescriptorDacl 挂到安全描述符上;
  5. 在另一个 1 KB 的 SaclBuffer 中初始化 SACL:
    • 用 AddScopedPolicyIDAce 添加 CAP ID ACE(传入 CapIDSid);
    • 遍历资源属性数组,逐个用 AddResourceAttributeAce 添加资源属性 ACE(sid 使用 EveryoneSID,claim 信息来自 CLAIM_SECURITY_ATTRIBUTES_INFORMATION 结构);
  6. SetSecurityDescriptorSacl 挂上 SACL;
  7. 调用 MakeSelfRelativeSD 把绝对形式的安全描述符转换为自相对形式:第一次调用以 NULL 缓冲探测所需大小(预期返回 ERROR_INSUFFICIENT_BUFFER),随后 LocalAlloc 分配并用第二次调用完成转换。

代码注释中还给出一个值得注意的实现限制:SACL 缓冲区是静态 1 KB,添加大量资源属性 ACE 时一旦超出该容量,AddResourceAttributeAce 就会失败;若业务上确实需要大量 ACE,应增大缓冲区或改用动态内存分配。

源码深度剖析:CAP ID 的校验

IsValidCapID(ResourceAttributesSample.cpp)负责把 CAP ID 字符串转换为可用 SID,其校验链条层层递进:

  1. ConvertStringSidToSid 把 S-1-17-... 形式的字符串转为二进制 SID;
  2. IsValidSid 校验 SID 结构合法性;
  3. 用 RtlEqualMemory 比较 SID 的标识机构(IdentifierAuthority)与 SECURITY_SCOPED_POLICY_ID_AUTHORITY,确保 CAP ID 必须属于 S-1-17 机构,否则报错 "The CAPID doesn't have the correct authority S-1-17";
  4. 调用 LsaGetAppliedCAPIDs(NULL, &AppliedCaps, &CapCount) 枚举本机已应用的 CAP,再用 EqualSid 逐一比对;
  5. 找到匹配则把 SID 返回给调用方;找不到则打印本机已应用的 CAP ID 列表(通过 ConvertSidToStringSid 转回字符串)并退出。

这一步正是 README 所述「CAP 必须已应用到本机」约束的代码落地,也是排查授权结果时最容易定位问题的地方。

源码深度剖析:资源属性的两种来源

从文件提取资源属性(-file)

GetFileResourceAttributes(ResourceAttributesSample.cpp)的工作方式:

  1. GetNamedSecurityInfo 以 ATTRIBUTE_SECURITY_INFORMATION 标志读取文件的属性安全信息(仅资源属性相关部分);
  2. ConvertSecurityDescriptorToStringSecurityDescriptor 把二进制安全描述符转为 SDDL 字符串;
  3. 在 SDDL 中查找资源属性 ACE 的特征签名 (RA; 来统计属性个数(小于 1 则提示 "The file ... does not have resource attributes" 并退出);
  4. 手工按分号逐段跳过 ACE 的 AceType / AceFlags / Rights / ObjectGuid / InheritObjectGuid / AccountSid 字段,截取到属性声明串;
  5. 依次调用 ParseResourceAttributeString(把 (Name,ValueType,Flags,Value1,...) 拆成四段)与 InterpretResourceAttribute(转换为可供 AddResourceAttributeAce 使用的 CLAIM_SECURITY_ATTRIBUTE_V1 结构),最后打印文件中的资源属性。

从命令行解析资源属性(-ra)

ParseResourceAttributesArguments(ResourceAttributesSample.cpp)则完全围绕字符串处理:

  • 要求参数以 ( 开头,并按 ( 的出现次数统计属性个数;
  • 对每个属性调用 ParseResourceAttributeString 与 InterpretResourceAttribute,同时把解析结果友好地打印到控制台。

两路来源最终都汇入统一的 PCLAIM_SECURITY_ATTRIBUTE_V1 结构数组,供 CreateSecurityDescriptor 使用。

源码深度剖析:值类型与 Claim 结构的构建

InterpretResourceAttribute(ResourceAttributesSample.cpp)把字符串形式的属性转换为内存中的 claim 结构,其中有两个细节值得开发者注意:

  • 值类型映射:头文件 ResourceAttributesSample.h 中的 GetValueType 将 SDDL 类型串映射到 Windows 定义的 CLAIM_SECURITY_ATTRIBUTE_TYPE_* 常量:
命令行类型串 对应常量 说明
TI(SDDL_INT) CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64 有符号 64 位整数
TU(SDDL_UINT) CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64 无符号 64 位整数
TS(SDDL_WSTRING) CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING 字符串
TB(SDDL_BOOLEAN) CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN 布尔值
其他 CLAIM_SECURITY_ATTRIBUTE_TYPE_INVALID 非法类型,报错退出
  • 连续内存布局:代码先按 ALIGN(sizeof(CLAIM_SECURITY_ATTRIBUTE_V1)) + ALIGN(NameBytes) + ALIGN(ValueCount * GetValueSize(ValueType)) 计算出所需总字节数,对字符串类型还要为每个字符串值追加空间,然后一次性 LocalAlloc 分配整块内存,把结构体、名字、值数组依次摆放在同一块连续缓冲区中,保证返回的 claim 结构可被 AddResourceAttributeAce 直接使用。解析时还会校验 Flags 与 CLAIM_SECURITY_ATTRIBUTE_VALID_FLAGS 的合法性(InterpretResourceAttribute 中 (Flags & 0x0000FFFF) & (~CLAIM_SECURITY_ATTRIBUTE_VALID_FLAGS) 不为 0 即报 ERROR: Invalid flags)。

源码深度剖析:AuthzAccessCheck 授权判定

PerformAccessCheck(ResourceAttributesSample.cpp)完整呈现了 Authz 授权检查的标准调用链:

  1. OpenProcessToken 打开当前进程令牌(TOKEN_QUERY 权限);
  2. AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT)初始化授权资源管理器;
  3. AuthzInitializeContextFromToken 基于令牌创建 Authz 客户端上下文;
  4. 构造 AUTHZ_ACCESS_REQUEST(DesiredAccess = MAXIMUM_ALLOWED,即查询该令牌在给定安全描述符下被授予的最大访问权)与 AUTHZ_ACCESS_REPLY(结果缓冲 1 KB,同时容纳 GrantedAccessMask 与 Error);
  5. 调用 AuthzAccessCheck,成功后把 AccessReply.GrantedAccessMask[0] 以 0x%08x 格式打印,即示例最终输出的 Access Mask。

该函数正是 README 中"displaying the access granted by AuthzAccessCheck"的具体实现;它是验证"CAP + 资源属性写入 SACL 后授权结果如何变化"的可观测入口。

关键 API 速查

API / 函数 用途 出处(仓库路径)
AddScopedPolicyIDAce 向 SACL 添加集中访问策略(CAP)ACE ResourceAttributesSample.cpp
AddResourceAttributeAce 向 SACL 添加资源属性 ACE ResourceAttributesSample.cpp
LsaGetAppliedCAPIDs 枚举本机已应用的 CAP ID ResourceAttributesSample.cpp
GetNamedSecurityInfo 读取文件属性安全信息(ATTRIBUTE_SECURITY_INFORMATION) ResourceAttributesSample.cpp
AuthzAccessCheck 执行授权检查并返回授予的访问掩码 ResourceAttributesSample.cpp
MakeSelfRelativeSD 将安全描述符转换为自相对格式 ResourceAttributesSample.cpp

常见问题与排查思路

结合源码中的错误处理分支,运行示例时可能遇到的主要问题及定位方式如下:

  • -cap 与 -file/-ra 组合不合法:wmain 会直接打印用法说明并退出(argc != 5、-file 与 -ra 同时给出、或 CAP ID 缺失都会触发)。
  • CAP ID 不在 S-1-17 机构:IsValidCapID 报 "The CAPID doesn't have the correct authority S-1-17"。请核对 CAP ID 是否形如 S-1-17-...。
  • CAP 未应用到本机:程序列出当前机器所有已应用的 CAP ID。此时需要在域环境中为该机器应用目标集中访问策略后重试。
  • 文件没有资源属性:GetFileResourceAttributes 报 "The file ... does not have resource attributes" 并退出。请确认目标文件确实被设置了资源属性(可通过属性编辑器或 SDDL 检查 (RA; 项)。
  • SACL 缓冲不足:资源属性过多时 AddResourceAttributeAce 失败(错误信息带 -Attribute = <名称>)。如注释所言,可增大 1 KB 的 SaclBuffer 或改用动态分配。
  • 需要域环境:涉及 CAP 与资源声明的完整功能必须在已定义声明类型与集中访问策略、且 CAP 已应用的 AD 环境中验证;普通单机环境只能看到用法说明或提前退出。

小结与延伸阅读

ResourceAttributes 示例把"集中访问策略(CAP)"与"资源属性(Resource Attribute)"两条动态访问控制主线收拢到一个可运行、可观测的命令行工具中:一端通过 AddScopedPolicyIDAce 与 AddResourceAttributeAce 构建安全描述符,另一端通过 AuthzAccessCheck 验证授权结果。对于要在 Windows 8.1 / Windows Server 2012 R2 及以上环境开发安全相关能力的开发者,这是一份结构清晰、可复用的参考实现。

仓库内可直接阅读的原始材料:

登录后查看全文
Windows-classic-samples