《windows核心编程》笔记(三)——第三章:内核对象:安全描述符SECURITY_DESCRIPTOR
安全对象Securable Object
安全对象Securable Object是拥有SD的Windows的对象。
所有的被命名的Windows的对象都是安全对象。一些没有命名的对象是安全对象,如:进程和线程,也有安全描述符SD。安全对象Securable Object是拥有SD的Windows的对象。
在 Windows系统中,其是用一个安全描述符(Security
Descriptors)的结构来保存其权限的设置信息,简称为SD,其在Windows
SDK中的结构名是“SECURITY_DESCRIPTOR”,这是包括了安全设置信息的结构体。一个安全描述符包含以下信息:
- 一个安全标识符(Security identifiers),其标识了该信息是哪个对象的,也就是用于记录安全对象的ID。简称为:SID。
- 一个DACL(Discretionary Access Control List),其指出了允许和拒绝某用户或用户组的存取控制列表。 当一个进程需要访问安全对象,系统就会检查DACL来决定进程的访问权。如果一个对象没有DACL,那么就是说这个对象是任何人都可以拥有完全的访问权 限。
- 一个SACL(System Access Control List),其指出了在该对象上的一组存取方式(如,读、写、运行等)的存取控制权限细节的列表。
- 还有其自身的一些控制位。
DACL和SACL构成了整个存取控制列表Access Control List,简称ACL,ACL中的每一项,我们叫做ACE(Access Control Entry)
访问控制链表(ACL)
一个 ACL 是一个 ACE 链表。每个 ACL 中的 ACE 标示一个托管以及指定允许的访问权限、否定或托管的设计。一个对象的安全描述符号包含两种类型的 ACL ,一个是 DACL ,一个是 SACL 。
一个目录访问控制链表( DACL )标示允许或拒绝访问一个安全对象的托管。当一个进程常识访问一个安全对象的时候,系统检查对象的 DACL 中的 ACE 来决定是否赋予访问权限。如果对象没有 DACL ,系统赋予完全的访问权限,如果对象的 DACL 没有 ACE ,那么系统拒绝所有访问对象的尝试,因为 DACL 不允许任何访问权限。系统检查 ACE 序列直到找到一到多个 ACE ,或者直到任何请求的访问权限被否定。更多的信息参见: DACL 怎么样控制一个对象的。对于创建 DACL 的信息,见创建一个 DACL 。
一个系统访问控制链表( SACL )是管理员登录尝试访问一个安全对象。每个 ACE 指定一个指定的托管尝试的访问类型,这个访问会导致系统产生一个安全事件日志。一个 SACL 中的 ACE 能够产生访问尝试失败或成功的时候产生评估记录,在将来的 release 中,一个 SACL 在一个未授权用户尝试访问一个对象的时候发出一个警告。更多 SACL 的信息参见 Audit Generation 和 SACL 访问权限。
不要尝试直接使用一个 ACL 来工作。为了确保 ACL 能够语义正确的,使用适当的函数来创建和操纵 ACL 。更多的信息参见:从 ACL 中取得信息和创建或修改一个 ACL 。
ACL 也提供访问控制微软的 Active Directory 目录服务对象。活动目录服务接口( ADSI )包括创建和修改 ACL 内容的程序。更多信息参见:控制访问活动目录对象
访问控制实体(ACE)
一个访问控制实体(ACE)是ACL中的一个元素。一个ACL可能包含0到多个ACE。每个ACE通过一个指定的托管来控制或监视一个对象。更多关于添加、删除、改变ACE,见修改ACL对象。
Window Me/98/95:不支持访问控制
有6种类型的ACE,三种被所有的安全对象支持,其他类型是对象特定的ACE,由目录服务对象支持。
所有ACE类型都包含下面访问控制信息:
- 一个安全标示符号(SID)来标示ACE应用的托管
- 一个访问掩码指定ACE控制的访问权限
- 一个指示ACE类型的标志位
- 一系列位标志决定是否子容器或对象可以继承ACE从基本对象到ACL附着的对象。
下表列出了三个所有安全对象支持的ACE类型:
Type |
Description |
访问拒绝ACE |
用在一个DACL中拒绝到一个托管的访问权限 |
访问允许ACE |
用在一个DACL中允许到托管的访问权限 |
系统评估ACE |
用在SACL中,当托管尝试检查指定访问权限时产生一个评估记录 |
对于对象指定的ACE,参见对象指定ACE。
注意,系统警告ACE对象现在不支持。
托管
一个托管是一个用户帐户、组帐户或登录会话到一个ACE应用。每个ACL中的ACE都有一个SID来标示托管。用户帐户包含人使用的帐户或程序帐户(例如,Window服务用来登录到本地计算机的)。组帐户不能用来登录到计算机,但在ACE中非常有用,用来拒绝或允许一到多个用户帐户的访问权限。一个登录SID表示当前的登录会话用来允许和拒绝访问权限,直到用户登出。
NT4.0和后来版本中访问控制函数使用TRUSTEE结构体来表示一个托管。这个结构体能使你用一个字符串或一个SID来表示一个托管。如果你使用一个名字,从TRUSTEE结构创建ACE的函数执行SID缓冲区分配工作并且查询SID对应的帐户名称。有两个助手函数,BuildTrusteeWithSid和BuildTrusteeWithName,可以用SID或名字来初始化一个TRUSTEE结构。BuildTrusteeWithObjectsAndSid和BuildTrusteeWithObjectsAndName允许你使用一个对象指定的ACE信息来初始化一个TRUSTEE结构体。其他3个助手函数,GetTrusteeFrorm、GetTrusteeName和GetTrusteeType,返回TRUSTEE结构各成员的值。
Window XP/2000:TRUSTEE的ptstrName成员可以是一个指向OBJECTS_AND_NAME或OBJECTS_AND_SID结构体的指针。这些结构体说明对象指定ACE的信息,除了托管的名字和SID之外。这可以象SetEntriesInAcl和GetExplicitEntriesFromAcl这样的函数存储对象指定ACE的信息到EXPLICIT_ACCESS结构体的Trustee成员中。
// TRUSTEE结构体: typedef struct _TRUSTEE { PTRUSTEE pMultipleTrustee; MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation; TRUSTEE_FORM TrusteeForm; TRUSTEE_TYPE TrusteeType; LPTSTR ptstrName; } TRUSTEE, *PTRUSTEE;
访问权限和访问掩码
一个访问权限是一个标志位对应到一个特殊操作集合,这个集合表示线程可以在安全对象上执行的操作。例如:注册表键KEY_SET_VALUE访问权限,对应到线程在这个键下设置的值的能力。如果线程想在一个对象执行一个操作,但没有必要的访问权限,系统不执行操作。
一个访问掩码是一个32位的值,它对应到对象支持的访问权限。所有的Windows NT/2000/XP 安全对象使用一个Windows访问掩码格式,这个格式包含下面的访问权限位:
- 通用访问权限
- 标准访问权限
- SACL访问权限
- 目录服务访问权限
当一个线程想打开一个对象的句柄,线程通常指定一个访问掩码来请求一系列的访问权限。例如,需要设置和查询注册表键的程序可以使用一个访问掩码来请求KEY_SET_VALUE和KEY_QUERY_VALUE访问权限来打开该键。
下面表显示了操作每种类型安全对象的信息:
对象类型 |
安全描述符号函数 |
NTFS上的文件或目录系统 |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
命名管道和匿名管道 |
|
控制台屏幕缓冲区 |
Not supported. |
进程和线程 |
|
文件映射对象 |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Windows管理对象 (windows工作站和桌面) |
|
注册表键 |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Windows服务 |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
本地和远程打印机 |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
网络共享 |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
进程间同步对象 (事件, mutexes, semaphores, and waitable timers) |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
作业对象 |
GetNamedSecurityInfo, SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
通用访问权限
安全对象使用Windows 访问掩码格式,四个高位说明通用访问权限。每个安全对象类型映射到这些位到一系列标准和对象特殊访问权限。例如:一个Window文件对象映射GENERIC_READ位到READ_CONTROL和SYNCHRONIZE 标准访问权限和FILE_READ_DATA、FILE_READ_EA和FILE_READ_ATTRIBUTES对象指定访问权限。其他类型对象映射GENERIC_READ位到与类型对象适应的一些访问权限。
你可以在你打开一个对象句柄的时候使用通用访问权限来指定需要的访问类型;这通常比指定所有对应标准和特指权限要简单。
下表显示了通用访问权限定义的常量。
常量 |
解释 |
GENERIC_ALL |
读、写和执行访问 |
GENERIC_EXECUTE |
执行 |
GENERIC_READ |
读 |
GENERIC_WRITE |
写 |
应用程序定义的私有安全访问对象能使用通用访问权限。
安全描述符(SD)
安全描述符包含了安全对象相关的安全信息。安全描述父由一个SECURITY_DESCRIPTOR结构体组成,它关联到一个安全对象。一个安全描述符包含下面的安全信息:
- 拥有者或基本组对象的安全ID(SIDs)DACL指定特殊用户或组的允许或拒绝的访问权限SACL指定对象通用评估记录尝试的访问类型一个控制位集合,说明安全描述符的含义或它每个成员
程序不需要直接操作安全描述符的内容。Windows API提供设置和返回安全描述符号的函数。另外,有用来创建和初始化一个新对象安全描述符号的函数。
Windows NT 3.51和更早的版本,Windows ME/98/95:参见:低级访问控制。
工作在活动目录对象上安全描述符程序能使用Windows的安全函数或ADSI(活动目录服务接口)提供的安全接口。更多ADSI的信息参见:访问控制如何在活动目录中工作?
DACL是怎么控制访问对象的?
当一个线程想访问一个安全对象时候,系统要么允许访问,要拒绝访问。如果对象没有DACL,系统赋予访问权限,否则系统查找DACL中的该线程的ACE。每个对象DACL中的ACE指定托管的访问允许或拒绝的访问权限,它可以是一个用户帐户、组帐户或登录会话。
系统比较ACE中的托管和线程访问标记的托管标示。一个访问标记包含SID来说明用户和用户输入的组帐户。一个标记也包含一个登录SID,来表示当前的登录会话。在访问检查期间,系统忽略不可用的组SID。更多可用不可用信息以及拒绝SID,见访问标记中的SID属性。
通常,系统使用现成的基本访问标记来请求访问。然而,如果现成正在扮演其他用户,系统使用现成的扮演标记。
系统现成序列中的每个ACE,直到下面的情况发生:
- 一个显式拒绝任何与线程访问标记中列出托管不同的访问ACE出现
- 线程访问标记中列出的一个或多个允许访问的ACE出现,这个标记显式赋予所有权限。
- 所有的ACE都检查过,并且至少一个访问权限没有显式的允许,这种情况下暗示拒绝。
下图描述一个DACL对象允许访问一个线程而拒绝访问另一个线程。
对于线程A,系统读ACE1并且立即拒绝访问,因为访问拒绝ACE应用到线程访问标记中的用户。这种情况下,系统并没有检查ACE2和3。对于线程B,ACE1没有应用,因此系统处理ACE2,其允许写操作,并且ACE3允许读和执行操作。
因为系统在请求访问并显式允许或拒绝的时候停止检查,DACL中的ACE顺序非常重要。注意,如果ACE顺序与例子中的不同,系统可能分配访问权限给A。对于系统对象,操作系统定义一个首选的DACL中ACE的顺序。
创建一个DACL
创建适当的 DACL 是你程序开发中必要和重要的,因为一个 NULL DACL 允许所有用户所有访问权限,不要使用 NULL DACL 。
下面例子演示了如何正常创建一个 DACL 。例子,包含一个函数, CreateMyDACL ,用安全描述符定义语言( SDDL )来定义那些赋予的和拒绝的 DACL 中的访问控制。为了提供对你程序对象的不同访问,根据你的需要来修改 CreateMyDACL 。
在例子中:
- main 函数传递一个 SECURITY_ATTRIBUTES 结构体的地址给 CreateMyDACL 函数
- CreateMyDACL 函数使用 SDDL 字符串来:
拒绝 guest 和匿名登录用户访问
允许授权用户的读 / 写 / 执行访问
允许管理员所有控制 - 更多的 SDDL 字符串格式
- CreateMyDACL 函数调用 ConvertStringSecurityDescriptorToSecurityDescriptor 函数转换 SDDL 字符串为一个安全描述符。安全描述符号通过 SECURITY_ATTRIBUTES 结构体的 lpSecurityDescriptor 成员来指定。 CreateMyDACL 发送 ConvertStringSecurityDescriptorToSecurityDescriptor 的返回值给 main 函数。
- main 函数完成使用 SECURITY_ATTRIBUTES 结构体时, main 函数释放 lpSecurityDescriptor 成员的内存,调用 LocalFree 函数来完成。
注意,成功编译 SDDL 函数,例如: ConvertStringSecurityDescriptorToSecurityDescriptor, 你必须定义 _WIN32_WINNT 常量为 0x0500 或更大。 SDDL 在 Windows 2003 家族 ,Windows XP 和 2000 操作系统上可用。
#define _WIN32_WINNT 0x0500 #include <windows.h> #include <sddl.h> #include <stdio.h> BOOL CreateMyDACL(SECURITY_ATTRIBUTES *); void main() { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = FALSE; // Call function to set the DACL. The DACL // is set in the SECURITY_ATTRIBUTES // lpSecurityDescriptor member. if (!CreateMyDACL(&sa)) { // Error encountered; generate message and exit. printf("Failed CreateMyDACL\n"); exit(1); } // Use the updated SECURITY_ATTRIBUTES to specify // security attributes for securable objects. // This example uses security attributes during // creation of a new directory. if (0 == CreateDirectory(TEXT("C:\\MyFolder"), &sa)) { // Error encountered; generate message and exit. printf("Failed CreateDirectory\n"); exit(1); } // Free the memory allocated for the SECURITY_DESCRIPTOR. if (NULL != LocalFree(sa.lpSecurityDescriptor)) { // Error encountered; generate message and exit. printf("Failed LocalFree\n"); exit(1); } } // CreateMyDACL. // Create a security descriptor that contains the DACL you want. // This function uses SDDL to make Deny and Allow ACEs. // // Parameter: // SECURITY_ATTRIBUTES * pSA // Pointer to a SECURITY_ATTRIBUTES structure. It is the caller\'s // responsibility to properly initialize the structure and to free // the structure\'s lpSecurityDescriptor member when the caller has // finished using it. To free the structure\'s lpSecurityDescriptor // member, call the LocalFree function. // // Return value: // FALSE if the address to the structure is NULL. // Otherwise, this function returns the value from the // ConvertStringSecurityDescriptorToSecurityDescriptor function. BOOL CreateMyDACL(SECURITY_ATTRIBUTES * pSA) { // Define the SDDL for the DACL. This example sets // the following access: // Built-in guests are denied all access. // Anonymous logon is denied all access. // Authenticated users are allowed read/write/execute access. // Administrators are allowed full control. // Modify these values as needed to generate the proper // DACL for your application. TCHAR * szSD = TEXT("D:") // Discretionary ACL TEXT("(D;OICI;GA;;BG)") // Deny access to built-in guests TEXT("(D;OICI;GA;;;AN)") // Deny access to anonymous logon TEXT("(A;OICI;GRGWGX;;;AU)") // Allow read/write/execute to authenticated users TEXT("(A;OICI;GA;;;BA)"); // Allow full control to administrators if (NULL == pSA) return FALSE; return ConvertStringSecurityDescriptorToSecurityDescriptor( szSD, SDDL_REVISION_1, &(pSA->lpSecurityDescriptor), NULL); }
可保安全对象
一个安全对象有一个安全描述符。所有有名字的 Windows 对象都是安全的。有些没有名字的对象,例如:进程和线程对象也有安全描述符。对于大多数安全对象来说,你可以在创建对象的时候指定一个对象的安全描述符。例如:你能在 CreateFile 和 CreateProcess 函数中指定安全描述符。
另外, Windows 安全函数是你能够为操作系统创建的而不是 Windows 创建的安全对象得到和设置安全信息。 Windows 安全函数也提供支持私有使用安全描述符和程序定义对象。更多关于私有安全对象信息参见客户服务器访问控制。
每个安全对象定义它自己的一系列访问权限,并且它拥有的映射通用访问权限。更多关于指定和通用访问权限的信息,参见对象类型总览。
下表显示了函数来操作通用安全对象的安全信息:
对象类型 |
安全描述符函数 |
NTFS文件系统的文件和目录 |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Named pipes Anonymous pipes |
GetSecurityInfo , SetSecurityInfo |
Processes Threads |
GetSecurityInfo , SetSecurityInfo |
File-mapping objects |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Window-management objects ( window stations and desktops) |
GetSecurityInfo , SetSecurityInfo |
Registry keys |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Windows services |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Local or remote printers |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Network shares |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Interprocess synchronization objects (events, mutexes, semaphores, and waitable timers) |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Job objects |
GetNamedSecurityInfo , SetNamedSecurityInfo, GetSecurityInfo, SetSecurityInfo |
Directory service objects |
These objects are handled by Active Directory Objects. See Active Directory Service Interfaces. |
登录会话
一个登录绘画从用户登录到计算机时候开始。所有登录会话中的进程有相同的基本访问标记。访问标记包含登录会话相关的安全上下文,包含用户的 SID 和登录标示以及登录 SID 。
登录 SID
一个安全标示符( SID )用来表示登录会话。你可以使用 DACL 中的登录 SID 来在登录会话过程中控制访问。登录 SID 只有在用户登出的时候无效。登录 SID 是计算机运行的时候唯一的。没有其他会话有系统的 SID 。然而,可能的登录 SID 集合在计算机启动的时候被复位。为了从访问标记中返回登录 SID ,为 TokenGroups 调用 GetTokenInformation 函数。
访问标记
一个访问标记包含登录会话的安全信息。系统在用户登录的时候创建一个安全标记,每个进程执行用户由于的标记副本。标记表示用户、用户组和用户权限。系统使用标记来控制访问安全对象和控制用户在本地计算机上执行各种系统相关操作。两种访问标记:私有的和伪装的。
伪装标记:
一个被创建来捕获客户进程安全信息、允许服务器在安全操作中伪装客户进程的访问标记。
主标记:
一个 Windows 内核创建的标记。可能分配给一个进程来表示默认的进程安全信息。