本文将介绍Windows文件系统中安全描述符(Security Descriptor)
Security Desciptor与Linux ACL
Windows安全描述符和Linux ACL(访问控制列表)都是操作系统用于管理文件和目录权限的机制。尽管它们的目的相似,但它们的实现和功能有所不同。以下是 Windows安全描述符和Linux ACL之间的一些主要区别:
- 操作系统:Windows安全描述符用于Microsoft Windows操作系统,而Linux ACL用于基于Linux的操作系统。
- 数据结构:Windows安全描述符使用一个复杂的数据结构,包括安全标识符(SID)、访问控制列表(ACL)和一组其他属性。Linux ACL由一系列的访问控制项(ACE)组成,每个ACE都包含一个标识符(用户或组)、权限和权限类型。
- 继承:Windows安全描述符可以支持多层次的权限继承,文件和目录可以从父目录继承权限。Linux ACL也支持继承,但实现方式略有不同,使用默认ACL来定义新文件和目录的权限。
- 权限管理:Windows安全描述符提供了详细的权限管理,包括读取、写入、执行、删除等多种操作。Linux ACL也提供了类似的权限管理,包括读取、写入和执行(rwx),但相对来说较为简单。
- 权限类型:Windows安全描述符包括两种类型的 ACL,即访问控制列表(DACL)和系统访问控制列表(SACL)。DACL用于控制文件和目录的访问权限,而 SACL用于记录对象的访问事件。Linux ACL只提供一种 ACL类型,用于控制访问权限。
- 用户和组:Windows和Linux都使用用户和组来管理权限,但它们的实现方式不同。Windows使用安全标识符(SID)来表示用户和组,而Linux使用用户 ID(UID)和组 ID(GID)。
- 工具和命令:Windows和Linux使用不同的工具和命令来管理文件和目录的权限。Windows中常用的工具包括图形界面和命令行工具,如“icacls”和“cacls”。在Linux系统中,可以使用命令如“chmod”、“chown”和“setfacl”等来管理权限。
尽管 Windows安全描述符和Linux ACL在实现和功能上有所不同,但它们的核心目标是相同的,即为操作系统提供一种灵活、安全的权限管理机制。
ACE字符串
Windows访问控制项(ACE)字符串是用于表示访问控制列表(ACL)中每个访问控制项的文本表示。它用于指定特定安全主体(如用户或组)的权限。ACE 字符串的结构包括以下几个组成部分:
- 信任主体(Trustee):表示安全主体的安全标识符(SID)或名称,通常是一个用户、组或计算机。信任主体可以是域用户、本地用户、内置用户(如 “SYSTEM” 或 “NETWORK SERVICE”)等。
- 访问权限:指定允许或拒绝的访问权限。这些权限通常包括读取、写入、执行、删除、修改权限等。权限可以用缩写表示,例如,”R” 代表读取权限,”W” 代表写入权限,”X” 代表执行权限等。
- 访问控制类型(Access Control Type):表示授予或拒绝权限的类型。通常有两种类型,即允许(Allow,用 “A” 表示)和拒绝(Deny,用 “D” 表示)。
- 继承标志(Inheritance Flags):指示ACE是否应用于子对象(如子文件夹和文件)。常用的继承标志包括 “OI”(仅对象继承)、”CI”(仅容器继承)和 “IO”(继承只)。这些标志可以组合使用,例如 “OI|CI” 表示ACE应用于子对象和容器。
- 附加选项(Optional Flags):ACE 字符串可以包含附加选项,如继承属性 “NP”(不传播继承)或 “ID”(继承的)。这些选项有时用于控制更复杂的继承和权限行为。
下面是一个示例ACE字符串:D:(A;OICI;FA;;;Administrator)
- “D” 代表它是一个访问控制项的描述符(Discretionary)。
- “A” 表示允许(Allow)权限。
- “OICI” 是继承标志,表示ACE应用于子对象(Object Inherit)和容器(Container Inherit)。
- “FA” 代表完全访问(Full Access)权限。
- “Administrator” 是信任主体,指示这个ACE应用于 “Administrator” 用户。
此示例表示允许 “Administrator” 用户对文件或文件夹及其子对象具有完全访问权限。
Security Descriptor WIN32 API
获取安全描述符
在C++中,你可以使用WindowsAPI来获取文件的完整安全描述符信息,包括SACL(系统访问控制列表)、DACL(访问控制列表)、属组(Group)和属主(Owner)。以下是一个示例,演示如何获取这些信息:
|
该函数实现了两件事:
- 使用
GetNamedSecurityInfoW
从文件中获取安全描述符指针PSECURITY_DESCRIPTOR pSecurityDescriptor
。 - 用
ConvertSecurityDescriptorToStringSecurityDescriptorW
将PSECURITY_DESCRIPTOR pSecurityDescriptor
中存放的所有信息(DACL、SACL、Owner和Group)序列化成ACE字符串。
psidOwner
、psidGroup
、pDacl
、pSacl
分别指向文件的Owner、Group、DACL、SACL。pSecurityDescriptor
指向文件的安全描述符信息,pSecurityDescriptor
可以看成包含了上述所有信息。在使用完pSecurityDescriptor
后需要用LocalFree()
释放掉pSecurityDescriptor
指向的资源。psidOwner
、psidGroup
、pDacl
、pSacl
如果不需要使用可以传入nullptr
,且不需要释放资源。
GetNamedSecurityInfoW
的第二个参数DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
用bitmap的形式声明要获取的安全描述符类型为DACL、Owner和Group。这里没有传入SACL_SECURITY_INFORMATION
是因为SACL的获取需要进程有管理员权限,且需要开启SE_SECURITY_NAME
权限,有关权限提升这部分内容后续章节再详细说明。
之前使用GetNamedSecurityInfoW
成功获取了pSecurityDescriptor
指针后就可以用ConvertSecurityDescriptorToStringSecurityDescriptorW
将它其中的内容序列化ACE字符串了,这里直接传入DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
序列化所有信息即可。
设置安全描述符
Windows提供SetNamedSecurityInfo
API用于设置安全描述符。设置安全描述符需要提供指向Owner、Group、DACL、SACL的指针,即上文GetNamedSecurityInfoW
获得的psidOwner
、psidGroup
、pDacl
、pSacl
。而对于上文序列化为字符串的安全描述符,则需要用ConvertStringSecurityDescriptorToSecurityDescriptor
先将它解析成pSecurityDescriptor
指针,再从中获得psidOwner
、psidGroup
、pDacl
、pSacl
。以下是从ACE字符串中设置安全描述符的具体实现:
bool SetSecurityDescriptorW(const std::wstring& wPath, const std::wstring& wSddlStr) |
以上代码虽然能编译成功,但是执行时会失败。首先执行上述代码需要管理员权限,其次,即使再管理员权限下运行,SetNamedSecurityInfo
依然可能返回ERROR_INVALID_OWNER
错误。可能ACE字符串解析出的Owner无效或不正确,可以用IsValidSid
检测Owner是否合法。也可能当前进程没有分配SeRestorePrivilege
特权。
权限提升
首先定义权限提升方法:static bool EnablePrivilege(const wchar_t* privilegeName)
{
HANDLE hToken;
char buf[sizeof(TOKEN_PRIVILEGES) * 2];
TOKEN_PRIVILEGES& tkp = *((TOKEN_PRIVILEGES*) buf);
if (!::OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken)) {
return false;
}
/* enable SeBackupPrivilege, SeRestorePrivilege */
if (!::LookupPrivilegeValue(
nullptr,
privilegeName,
&tkp.Privileges[0].Luid)) {
return false;
}
if (!::LookupPrivilegeValue(
nullptr,
SE_RESTORE_NAME,
&tkp.Privileges[1].Luid)) {
return false;
}
tkp.PrivilegeCount = 2;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tkp.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
::AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), nullptr, nullptr);
return true;
}
然后在调用上文提到的API之前调用EnablePrivilege(SE_RESTORE_NAME)
提升进程权限:int main() {
// To get SACL
if (!EnablePrivilege(SE_SECURE_NAME)) {
std::cerr << "Error enabling SeSecurePrivilege" << std::endl;
return -1;
}
// Set ACL
if (!EnablePrivilege(SE_RESTORE_NAME)) {
std::cerr << "Error enabling SeRestorePrivilege" << std::endl;
return -1;
}
// invoke GetSecurityDescriptorW or SetSecurityDescriptorW here ...
return 0;
}