\0x1 简单的权限与传播继承
- 运用于
NTFS
的权限继承与传播是通过安全描述符来实现的,安全描述符的作用是控制对对象的访问。以文件夹为例,可以把文件夹看作一种容器,容器可以包含子容器和文件,只要父容器的安全描述符发生更改,并且安全描述符是可以传播的,那么子容器和子文件就可能发生更改。 - 创建一个父文件夹
father
和一个子文件夹child
,之后查询child
安全描述符的传播和DACL
中ACE
的继承状态。
int main(int argc, char *argv[])
{
SECURITY_INFORMATION requestedInfo = DACL_SECURITY_INFORMATION;
PSECURITY_DESCRIPTOR pSecDes = NULL; DWORD secSize = 0;
CHAR filePath[MAX_PATH] = "C:\\Users\\Cxy\\Desktop\\father\\child";
if (!GetFileSecurityA(filePath, requestedInfo, pSecDes, secSize, &secSize))
{
pSecDes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, secSize);
if (!GetFileSecurityA(filePath, requestedInfo, pSecDes, secSize, &secSize))
cout << GetLastError() << endl;
}
SECURITY_DESCRIPTOR_CONTROL pControl; DWORD revision;
if (GetSecurityDescriptorControl(pSecDes, &pControl, &revision))
{
if ((pControl & SE_DACL_PROTECTED) != SE_DACL_PROTECTED)
cout << "安全描述符是否可以传播: 可以传播" << endl;
}
BOOL daclPresent; BOOL daclDefaulted; PACL lpACL = NULL;
if (!GetSecurityDescriptorDacl(pSecDes, &daclPresent, &lpACL, &daclDefaulted))
cout << GetLastError() << endl;
ACL_SIZE_INFORMATION aclInfo; DWORD aclInfoSize = sizeof(ACL_SIZE_INFORMATION);
ACE_HEADER *aceHeader = NULL; ACCESS_ALLOWED_ACE *accessAllowed = NULL; LPVOID unknown = NULL;
if (GetAclInformation(lpACL, &aclInfo, aclInfoSize, AclSizeInformation))
{
for (int i = 0; i < aclInfo.AceCount; i++)
{
if (GetAce(lpACL, i, &unknown))
{
aceHeader = (ACE_HEADER *)unknown;
if ((aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE) || (aceHeader->AceType == ACCESS_DENIED_ACE_TYPE))
{
accessAllowed = (ACCESS_ALLOWED_ACE *)unknown;
cout << " ACE[" << i << "]: " << endl;
cout << " > 类型: " << (INT)accessAllowed ->Header.AceFlags << endl;
cout << " > SID 账户名: " << SIDToName(&accessAllowed->SidStart) << endl;
}
}
}
}
return 0;
}
PSTR WINAPI SIDToName(PSID lpSID)
{
LPSTR userName = NULL; LPSTR domainName = NULL; DWORD nameSize = 0; DWORD domainSize = 0; SID_NAME_USE peUse;
if (!LookupAccountSidA(NULL, lpSID, userName, &nameSize, domainName, &domainSize, &peUse))
{
userName = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nameSize);
domainName = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, domainSize);
if (!LookupAccountSidA(NULL, lpSID, userName, &nameSize, domainName, &domainSize, &peUse))
cout << GetLastError() << endl;
}
return userName;
}
- 上面代码的执行结果如下所示,使用
GetSecurityDescriptorControl
函数获取关于继承的标志位,只要继承的标志位不含SE_DACL_PROTECTED
就说明该安全描述符的DACL
是可以传播的。使用GetAce
来查看ACE Header
中的类型,可以看出三个ACE
的类型都是3
也就是包含CONTAINER_INHERIT_ACE
标志位,说明该ACE
相对于子容器来说是可以继承。
// https://docs.microsoft.com/en-us/windows/win32/api/accctrl/ns-accctrl-explicit_access_a
安全描述符是否可以传播: 可以传播
ACE[0]:
类型: 3
SID 账户名: SYSTEM
ACE[1]:
类型: 3
SID 账户名: Administrators
ACE[2]:
类型: 3
SID 账户名: Cxy
- 接下来创建两个新账户
cxy2
和cxy3
,并且分别向father
和child
文件夹添加这两个用户
LPVOID WINAPI EasyAlloc(INT size);
int main(int argc, char *argv[])
{
// 获取文件安全描述符
SECURITY_INFORMATION requestInfor = DACL_SECURITY_INFORMATION; DWORD secSize = 0;
CHAR filePath[MAX_PATH] = "C:\\Users\\Cxy\\Desktop\\father\\child"; PSECURITY_DESCRIPTOR pSecDes = NULL;
if (!GetFileSecurityA(filePath, requestInfor, pSecDes, secSize, &secSize))
{
pSecDes = Alloc(secSize);
if (!GetFileSecurityA(filePath, requestInfor, pSecDes, secSize, &secSize))
cout << GetLastError() << endl;
}
// 转换安全描述符的格式
SECURITY_DESCRIPTOR *pSecDesSelf = NULL; DWORD size = 0;
PACL lpDACL = NULL; PACL lpSACL = NULL; PSID lpOwner = NULL; PSID lpPrimaryGroup = NULL;
DWORD daclSize = 0; DWORD saclSize = 0; DWORD ownerSize = 0; DWORD primaryGroupSize = 0;
if (!MakeAbsoluteSD(pSecDes, pSecDesSelf, &size, lpDACL, &daclSize, lpSACL, &saclSize, lpOwner, &ownerSize, lpPrimaryGroup, &primaryGroupSize))
{
pSecDesSelf = (SECURITY_DESCRIPTOR *)Alloc(size);
lpDACL = (PACL)Alloc(daclSize); lpSACL = (PACL)Alloc(saclSize);
lpOwner = (PSID)Alloc(ownerSize); lpPrimaryGroup = (PSID)Alloc(primaryGroupSize);
if (!MakeAbsoluteSD(pSecDes, pSecDesSelf, &size, lpDACL, &daclSize, lpSACL, &saclSize, lpOwner, &ownerSize, lpPrimaryGroup, &primaryGroupSize))
cout << GetLastError() << endl;
}
// 获取 cxy* 帐户名的 SID
PSID sid = NULL; DWORD cbSid = 0; LPSTR domainName = NULL; DWORD domainNameSize = 0;
CHAR account[MAX_PATH] = "cxy3"; SID_NAME_USE peUse;
if (!LookupAccountNameA("", account, sid, &cbSid, domainName, &domainNameSize, &peUse))
{
sid = (PSID)Alloc(cbSid); domainName = (LPSTR)Alloc(domainNameSize);
if (!LookupAccountNameA("", account, sid, &cbSid, domainName, &domainNameSize, &peUse))
cout << GetLastError() << endl;
}
// 构建新的 DACL
TRUSTEE_A trustee; EXPLICIT_ACCESS_A userAccess; PACL newDacl;
BuildTrusteeWithSidA(&trustee, sid);
userAccess.grfAccessPermissions = MY_READ_ATTRIBUTES; userAccess.grfAccessMode = SET_ACCESS;
userAccess.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; userAccess.Trustee = trustee;
if (ERROR_SUCCESS == SetEntriesInAclA(1, &userAccess, pSecDesSelf->Dacl, &newDacl))
{
pSecDesSelf->Dacl = newDacl;
}
else
cout << GetLastError() << endl;
// 重新设置文件对象的可传播的安全描述符, SetSecurityInfo 也可以将安全描述符设置为可传播
if (ERROR_SUCCESS != SetNamedSecurityInfo(filePath, SE_FILE_OBJECT, requestInfor, pSecDesSelf->Owner, pSecDesSelf->Group, pSecDesSelf->Dacl, pSecDesSelf->Sacl))
cout << GetLastError() << endl;
return 0;
}
LPVOID WINAPI EasyAlloc(INT size)
{
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
}
这里附上高级权限的掩码
#define MY_CONTROL_FULLY (0x1F01FF) // 00000000 00001111 00000001 11111111 完全控制
#define MY_READ_DATA (0x1) // 00000000 00000000 00000000 00000001 列出文件夹/读取数据
#define MY_WRITE_DATA (0x2) // 00000000 00000000 00000000 00000010 创建文件/写入数据
#define MY_ADDITIONAL_DATA (0x4) // 00000000 00000000 00000000 00000100 创建文件夹/附加数据
#define MY_READ_EXTENDED_DATA (0x8) // 00000000 00000000 00000000 00001000 读取扩展数据
#define MY_WRITE_EXTENDED_DATA (0x10) // 00000000 00000000 00000000 00010000 写入扩展属性
#define MY_EXECUTABLE_FILE (0x20) // 00000000 00000000 00000000 00100000 遍历文件夹/执行文件
#define MY_READ_ATTRIBUTES (0x80) // 00000000 00000000 00000000 10000000 读取属性
#define MY_WRITE_ATTRIBUTES (0x100) // 00000000 00000000 00000001 00000000 写入数据
#define MY_DELETE (0x10000) // 00000000 00000001 00000000 00000000 删除
#define MY_READ_PERMISSION (0x20000) // 00000000 00000010 00000000 00000000 读取权限
#define MY_CHANGE_PERMISSION (0x40000) // 00000000 00000100 00000000 00000000 更改权限
#define MY_TAKE_OWNERSHIP (0x80000) // 00000000 00001000 00000000 00000000 取得所有权
- 最后在
child
文件夹下创建test.txt
文件,可以看出它继承了father
和child
的安全描述符,这就是 NTFS 下安全描述符的基本继承和传播规则。
\0x2 继承与传播
- 通过
SetSecurityDescriptorControl
设置安全描述符拥有SE_DACL_PROTECTED
位是阻止安全描述符进行传播的方法之一,但是并不影响子对象的继承,当子对象刚创建时会继承父对象的安全描述符,但是之后怎么更改父对象的安全描述符都与子对象无关,因为父对象禁用了传播。
\0x3 显示权限与继承权限
- 显示权限一般是非继承权限,
child
文件夹既拥有father
继承下来的ACE cxy2
,也有自己显式创建的ACE cxy3
。那么显示权限是自己创建的当然可以更改,但是继承的权限可以更改吗?应该不能把,不然要继承干嘛,而且更改的时候也是灰色的,貌似不可以更改。那通过调用WIN32 API
呢?先试一试能不能删除继承的ACE cxy2
。
LPVOID WINAPI EasyAlloc(INT size);
int main(int argc, char *argv[])
{
// 获取文件安全描述符
SECURITY_INFORMATION requestInfor = DACL_SECURITY_INFORMATION; DWORD secSize = 0;
CHAR filePath[MAX_PATH] = "C:\\Users\\Cxy\\Desktop\\father\\child"; PSECURITY_DESCRIPTOR pSecDes = NULL;
if (!GetFileSecurityA(filePath, requestInfor, pSecDes, secSize, &secSize))
{
pSecDes = Alloc(secSize);
if (!GetFileSecurityA(filePath, requestInfor, pSecDes, secSize, &secSize))
cout << GetLastError() << endl;
}
// 转换安全描述符的格式
SECURITY_DESCRIPTOR *pSecDesSelf = NULL; DWORD size = 0;
PACL lpDACL = NULL; PACL lpSACL = NULL; PSID lpOwner = NULL; PSID lpPrimaryGroup = NULL;
DWORD daclSize = 0; DWORD saclSize = 0; DWORD ownerSize = 0; DWORD primaryGroupSize = 0;
if (!MakeAbsoluteSD(pSecDes, pSecDesSelf, &size, lpDACL, &daclSize, lpSACL, &saclSize, lpOwner, &ownerSize, lpPrimaryGroup, &primaryGroupSize))
{
pSecDesSelf = (SECURITY_DESCRIPTOR *)Alloc(size);
lpDACL = (PACL)Alloc(daclSize); lpSACL = (PACL)Alloc(saclSize);
lpOwner = (PSID)Alloc(ownerSize); lpPrimaryGroup = (PSID)Alloc(primaryGroupSize);
if (!MakeAbsoluteSD(pSecDes, pSecDesSelf, &size, lpDACL, &daclSize, lpSACL, &saclSize, lpOwner, &ownerSize, lpPrimaryGroup, &primaryGroupSize))
cout << GetLastError() << endl;
}
if (!DeleteAce(pSecDesSelf->Dacl, 2))
cout << GetLastError() << endl;
// 重新设置文件对象的可传播的安全描述符
if (ERROR_SUCCESS != SetNamedSecurityInfo(filePath, SE_FILE_OBJECT, requestInfor, pSecDesSelf->Owner, pSecDesSelf->Group, pSecDesSelf->Dacl, pSecDesSelf->Sacl))
cout << GetLastError() << endl;
return 0;
}
LPVOID WINAPI EasyAlloc(INT size)
{
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
}
- 好像不行诶,不能删除继承的权限。除非使用
GetSecurityDescriptorControl() -> SE_DACL_PROTECTED
禁用father
的继承才行,那么修改其中的权限呢?当前也是不行的。当新的安全描述符中的DACL
喂给系统时首先忽略继承的ACE
,之后才可能会对显示ACE
进行更改。如果非要更改继承ACE
,可以沿着继承树往上找到想更改的显示ACE
,就像father
的ACE cxy2
,再由系统沿继承树传播下来,这样child
继承的ACE cxy2
也会得到更改。
\0x4 ACL 中 ACE 的顺序
- 在
ACL
中显示ACE
优先于继承ACE
,在显示和继承ACL
中,拒绝的ACE
优先于允许ACE
。