用于保护可保护对象的安全描述符是以二进制形式存储的,但Windows API也提供了可以将安全描述符在二进制与文本字符串之间来回转换的函数。要将二进制形式存储的安全描述符转换成字符串形式,可调用ConvertSecurityDescriptorToStringSecurityDescriptor()函数,而ConvertStringSecurityDescriptorToSecurityDescriptor()则可以进行反向转换。字符串形式的安全描述符虽不能起到保护可保护对象的作用,但它更易于存储和传送。
本篇尝试讲解一下以字符串形式存在的安全描述符所采用的格式规范,这在创建安全描述符以及在命令行用icacls.exe命令操作安全描述符时是非常有帮助的。由于安全描述符中的许多元素,如访问权限掩码(Access Rights Mask)、SID等都要用字符串表示,所以本文先要简单介绍一下访问权限和SID,再介绍安全描述符的字符串形式所采用的格式规范,最后再介绍一下icacls.exe命令。本文结构如下:
- 访问权限
- SID
- 安全描述符定义语言(SDDL)
- ICACLS.EXE
一、访问权限
在之前的文章中我们提到,安全描述符中的ACE要描述针对某受信任实体所授予或拒绝的权限,这些权限是以访问权限掩码(Access Rights Mask)的形式表达的。访问权限掩码是一个32位的值,其中的每个位都对应可保护对象的访问权限,形式如下:
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
GR | GW | GE | GA | 保留 | AS | 标准访问权限 | 对象特定权限 |
各种访问权限实际上是其中一个一个的标志位,代表可以在可保护对象上执行的一组操作。例如注册表键作为可保护对象,它定义了KET_SET_VALUE权限,拥有此权限的线程可以在该键下设置值。不同类型的可保护对象都根据其自身特性定义了适用于自己的一套访问权限。如果线程希望在某可保护对象执行某种操作却不具备相应的权限,系统会拒绝执行该操作。
在访问掩码中,低16位用于对象特定的访问权限,再向上8位是标准访问权限,最高4位用于通用访问权限。24位代表ACCESS_SYSTEM_SECURITY,用于访问对象安全描述符中的SACL。
访问权限掩码的高4位用于指定通用访问权限(Generic Access Rights),分别是
- GR(GENERIC_READ,读操作)(31位)
- GW(GENERIC_WRITE,写操作)(30位)
- GE(GENERIC_EXECUTE,执行操作)(29位)
- GA(GENERIC_ALL,指代所有可能的权限)(28位)
线程在访问可保护对象时系统会将这些通用权限映射到标准权限或对象特定权限。比如文件对象会将通用权限GENERIC_READ映射成READ_CONTROL和SYNCHRONIZE标准权限,以及FILE_READ_DATA、FILE_READ_EA和FILE_READ_ATTRIBUTES这些文件对象特有的访问权限。对于其他类型的对象系统也会根据其类型将GENERIC_READ映射成相应的权限。
当调用类似CreateFile()、OpenProcess()的函数打开某可保护对象的句柄时,可在函数入口参数指定所请求的权限(也有特例,比如CreateSemaphore()函数不需要明确指定所需权限,它要求对所打开对象进行完全访问的)。所请求的权限应以能够满足需求为准,尽量不要请求完全访问,只要通用权限能够满足要求,指定通用权限作为希望采取的访问类型会比指定具体的标准权限或对象特定权限简便得多。
通用权限的抽象层次较高,与对象类型的特定权限相比,还有一组适用于大多数对象类型、抽象层次适中的标准访问权限(Standard Access Rights):
权限 | 说明 |
DELETE | 用于删除对象的权限 |
READ_CONTROL | 读取对象安全描述符中除SACL之外的信息所需的权限 |
SYNCHRONIZE | 将受保护对象用于同步机制的权限。被授予该权限的线程可以在受保护对象转为信号状态前处于等待状态,具体可参考Windows同步机制的相关资料。并不是所有对象类型都支持该权限。 |
WRITE_DAC | 用于修改受保护对象安全描述符中DACL的权限 |
WRITE_OWNER | 用于修改受保护对象安全描述符中所有者的权限 |
为方便使用,Windows根据常见的应用场景将以上标准权限又做了以下组合:
权限组合 | 说明 |
STANDARD_RIGHTS_ALL | 组合DELETE、READ_CONTROL、WRITE_DAC、WRITE_OWNER、SYNCHRONIZE |
STANDARD_RIGHTS_EXECUTE | 当前等同于READ_CONTROL |
STANDARD_RIGHTS_READ | 当前等同于READ_CONTROL |
STANDARD_RIGHTS_REQUIRED | 组合DELETE、READ_CONTROL、WRITE_DAC、WRITE_OWNER |
STANDARD_RIGHTS_WRITE | 当前等同于READ_CONTROL |
24位是ACCESS_SYSTEM_SECURITY权限,用于控制线程获取和设置安全描述符中SACL的能力,该权限仅可用于SACL,不可用于DACL中。只有当线程的访问令牌中被赋予SE_SECURITY_NAME特权时才能获得该访问权限。
另外,Active Directory目录服务中的对象也都有一个安全描述符,在其中可以设置一组目录服务对象特有的权限,见下表:
权限 | 说明 |
ACTRL_DS_OPEN | 打开DS对象 |
ACTRL_DS_CREATE_CHILD | 创建子DS对象 |
ACTRL_DS_DELETE_CHILD | 删除子DS对象 |
ACTRL_DS_LIST | 枚举DS对象 |
ACTRL_DS_READ_PROP | 读取DS对象的属性 |
ACTRL_DS_WRITE_PROP | 写入DS对象的属性 |
ACTRL_DS_SELF | 只有在执行对象支持的已验证权限检查并通过之后,才允许访问。 此标志可以单独用于执行对象的所有已验证权限检查,也可以与特定已验证权限的标识符结合使用,表示仅对该已验证权限检查。 |
ACTRL_DS_DELETE_TREE | 删除对象的树 |
ACTRL_DS_LIST_OBJECT | 列出对象的树 |
ACTRL_DS_CONTROL_ACCESS | 只有在对对象支持的扩展权限进行检查并通过后才允许访问。该标志可单独使用,表示要检查所有的扩展权限,也可以和某一扩展权限的标识符结合使用,表示只对这个指定的扩展权限进行检查。 |
二、SID
SID是用来识别受信任实体的一个可变长度的数值,可以理解为受信任实体的身份证号,要求具有唯一性。Windows安全机制中SID被广泛应用,在安全描述符中、ACE以及访问令牌中都用SID指代受信任实体。安全描述符中的对象所有者及其所属组均用其SID表示,在ACE中被授予或拒绝权限的受信任实体也用其SID表示,在访问令牌中线程所代表的用户及其所属组同样也是用其SID表示。
在如此多的SID中,除了被唯一创建并赋予特定用户和组的SID外,还有一种公认SID(Well-known SID),它们用来标识通用用户和组,比如众所周知的“Everyone"(或World)组被赋予的就是公认SID。公认SID的名称有可能会变化,英文版Windows中的内置管理组”BUILTIN\Administrators“在其他语言的版本中可能有不同的名称,但它们都是用一个共同的公认SID。基于这个原因,也是基于惯例,在创建自己的SID时要基于系统预定义的常数,而且要用系统提供的API函数惭怍SID,不要直接编程操作SID的数据结构。文后会附上用于操作SID的函数集。
1.SID的组成