Windows-classic-samples 之 ResourceAttributes 示例:CAP 与资源属性安全描述符构建及 AuthzAccessCheck 实战
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)能力的核心组成部分。本示例把两者结合起来:
- 将一个 CAP ID 以
Scoped Policy ID形式写入安全描述符的 SACL; - 将一个或多个资源属性以资源属性 ACE(
(RA;...))形式写入 SACL; - 基于该安全描述符调用
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 给出的构建步骤:
- 使用 Visual Studio 2013 for Windows 8.1(任意 SKU)或更新版本的 Visual Studio 打开
ResourceAttributesSample.sln; - 加载完成后按 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 的实际调用顺序可以还原完整流程:
- 读取并校验 CAP ID:调用
IsValidCapID将 CAP ID 字符串转为 SID,并检查它是否已应用到本机;- 若已应用,则可在访问检查中使用;
- 若未应用,则把本机已应用的 CAP 列表打印出来并退出。
- 获取资源属性(二选一):
- 若提供了
-file,调用GetFileResourceAttributes读取文件的安全描述符,提取其中的资源属性 ACE,打印后用于构建安全描述符;若文件没有资源属性,则退出; - 若提供了
-ra,调用ParseResourceAttributesArguments解析命令行中的资源属性字符串。
- 若提供了
- 构建安全描述符:调用
CreateSecurityDescriptor,生成包含 CAP ID ACE 与资源属性 ACE 的自相对(self-relative)安全描述符。 - 执行访问检查:调用
PerformAccessCheck,用当前进程令牌初始化 Authz 客户端上下文,对该安全描述符调用AuthzAccessCheck,并把得到的授权结果(Access Mask)打印到控制台。
源码深度剖析:安全描述符的构建
CreateSecurityDescriptor(ResourceAttributesSample.cpp)是示例的核心,它按以下步骤组装安全描述符:
InitializeSecurityDescriptor初始化空的安全描述符;- 通过
AllocateAndInitializeSid创建 System SID(SECURITY_LOCAL_SYSTEM_RID)与 Everyone SID(SECURITY_WORLD_RID); SetSecurityDescriptorOwner/SetSecurityDescriptorGroup把所有者与主组都设为 System;- 在栈上 1 KB 的
DaclBuffer中初始化 DACL,并用AddAccessAllowedAce添加一条授予 Everyone 完全控制(KEY_ALL_ACCESS)的允许 ACE,再SetSecurityDescriptorDacl挂到安全描述符上; - 在另一个 1 KB 的
SaclBuffer中初始化 SACL:- 用
AddScopedPolicyIDAce添加 CAP ID ACE(传入CapIDSid); - 遍历资源属性数组,逐个用
AddResourceAttributeAce添加资源属性 ACE(sid 使用 EveryoneSID,claim 信息来自CLAIM_SECURITY_ATTRIBUTES_INFORMATION结构);
- 用
SetSecurityDescriptorSacl挂上 SACL;- 调用
MakeSelfRelativeSD把绝对形式的安全描述符转换为自相对形式:第一次调用以NULL缓冲探测所需大小(预期返回ERROR_INSUFFICIENT_BUFFER),随后LocalAlloc分配并用第二次调用完成转换。
代码注释中还给出一个值得注意的实现限制:SACL 缓冲区是静态 1 KB,添加大量资源属性 ACE 时一旦超出该容量,AddResourceAttributeAce 就会失败;若业务上确实需要大量 ACE,应增大缓冲区或改用动态内存分配。
源码深度剖析:CAP ID 的校验
IsValidCapID(ResourceAttributesSample.cpp)负责把 CAP ID 字符串转换为可用 SID,其校验链条层层递进:
ConvertStringSidToSid把S-1-17-...形式的字符串转为二进制 SID;IsValidSid校验 SID 结构合法性;- 用
RtlEqualMemory比较 SID 的标识机构(IdentifierAuthority)与SECURITY_SCOPED_POLICY_ID_AUTHORITY,确保 CAP ID 必须属于 S-1-17 机构,否则报错"The CAPID doesn't have the correct authority S-1-17"; - 调用
LsaGetAppliedCAPIDs(NULL, &AppliedCaps, &CapCount)枚举本机已应用的 CAP,再用EqualSid逐一比对; - 找到匹配则把 SID 返回给调用方;找不到则打印本机已应用的 CAP ID 列表(通过
ConvertSidToStringSid转回字符串)并退出。
这一步正是 README 所述「CAP 必须已应用到本机」约束的代码落地,也是排查授权结果时最容易定位问题的地方。
源码深度剖析:资源属性的两种来源
从文件提取资源属性(-file)
GetFileResourceAttributes(ResourceAttributesSample.cpp)的工作方式:
GetNamedSecurityInfo以ATTRIBUTE_SECURITY_INFORMATION标志读取文件的属性安全信息(仅资源属性相关部分);ConvertSecurityDescriptorToStringSecurityDescriptor把二进制安全描述符转为 SDDL 字符串;- 在 SDDL 中查找资源属性 ACE 的特征签名
(RA;来统计属性个数(小于 1 则提示"The file ... does not have resource attributes"并退出); - 手工按分号逐段跳过 ACE 的
AceType / AceFlags / Rights / ObjectGuid / InheritObjectGuid / AccountSid字段,截取到属性声明串; - 依次调用
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 授权检查的标准调用链:
OpenProcessToken打开当前进程令牌(TOKEN_QUERY权限);AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT)初始化授权资源管理器;AuthzInitializeContextFromToken基于令牌创建 Authz 客户端上下文;- 构造
AUTHZ_ACCESS_REQUEST(DesiredAccess = MAXIMUM_ALLOWED,即查询该令牌在给定安全描述符下被授予的最大访问权)与AUTHZ_ACCESS_REPLY(结果缓冲 1 KB,同时容纳GrantedAccessMask与Error); - 调用
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 及以上环境开发安全相关能力的开发者,这是一份结构清晰、可复用的参考实现。
仓库内可直接阅读的原始材料:
- 示例说明文档:Samples/Security/ResourceAttributes/README.md
- 主源码(含全部函数实现与注释):Samples/Security/ResourceAttributes/cpp/ResourceAttributesSample.cpp
- 头文件(函数声明、SDDL 类型映射宏、
RESOURCE_ATTRIBUTE结构):Samples/Security/ResourceAttributes/cpp/ResourceAttributesSample.h - 工程与解决方案文件:cpp/ResourceAttributesSample.vcxproj、cpp/ResourceAttributesSample.sln