使用 C++ 递归扫描目录上的 ACL

Posted

技术标签:

【中文标题】使用 C++ 递归扫描目录上的 ACL【英文标题】:Recursive scan for ACLs on directories with C++ 【发布时间】:2013-02-27 19:37:43 【问题描述】:

我对某事感到好奇。我正在用 C++ 编写一段代码,它需要扫描特定文件夹的子文件夹的 ACL 权限,并找到与第一个文件夹的 ACL 权限不同的那些。为此,我使用FindFirstFile 和FindNextFile 递归检查文件夹中的所有文件夹,然后通过在找到的每个子文件夹上调用GetNamedSecurityInfo 来检查ACL 权限。

此方法有效,只是它运行速度很慢,尤其是在扫描网络共享时。我知道名为accesschk 的工具可以做同样的事情,但是当我在同一个文件夹上递归运行它时(使用-dsqvli 开关),它返回的结果比我上面描述的过程更快。

所以我想知道如何加快此 ACL 权限查找过程?

我的第一个想法是在 ACE 上使用继承,我只是不确定如何实现它......

编辑 2: 感谢 @arx 的建议。这个 ACL/ACE 东西的文档记录很差。他在下面发布的代码对我有用。请注意,我用于检查 ACL 继承的原始代码没有产生可靠的结果,原因是他在下面的帖子中概述了 @arx 的原因。

【问题讨论】:

如果您在运行工具后运行 accesschk,它总是会更快,因为所有信息都将在缓存中。如果这不是问题,请分析您的应用程序。它可能花时间解码 ACL(也许您没有缓存 SID 查找?),而不是实际检索安全描述符。它可能会花费时间将大量文本输出到控制台,因为(与 accesschk 不同)它不会忽略可继承的 ACE。管他呢。除非你知道它哪里慢,否则你不能指望修复它。 谢谢。我同意。问题是我在客户端机器(没有我的开发工具)上遇到了这个问题。我可以用什么来分析那里的应用程序? 我不知道有一种分析工具可以生成可以在客户端计算机上运行的独立检测应用程序。也许是一个问题。 @arx:我确实做了一些分析。事实证明,如果我调用GetEffectiveRightsFromAcl API 来检查每个文件夹的 ACL 权限,则几乎需要 90% 的总时间。有什么方法可以查看文件夹是否从父文件夹继承 ACE 从而不检查其 ACL 权限? GetEffectiveRightsFromAcl 没有什么特别的原因会变慢,除非您调用它过于频繁(即每个目录多次)或者您正在按名称识别受托人。后者会很慢。 【参考方案1】:

来自 cmets 的更多信息:

应用程序确定正在检查其访问权限的用户的 SID。

在每个目录上调用了GetNamedSecurityInfo,应用程序使用用户的SID 调用GetEffectiveRightsFromAcl。花费大部分时间的是后一个调用。

GetEffectiveRightsFromAcl 根据用户的 SID 和用户所属的任何组的 SID 检查 ACL。这可能很慢,因为确定用户组需要往返域控制器。

有两种可能的修复方法和一条死胡同:

模拟 GetEffectiveRightsFromAcl

在循环之外,确定用户的 SID 和用户组的 SID。 (TODO:检查嵌套组是否自动处理,或者是否必须递归解析。)

要确定 ACL 的有效权限:

    使用 ACCESS_MASK(实际上是 DWORD)来表示权限掩码。将其初始化为零。 以相反的顺序处理 ACL 中的 ACE。这可确保较早的 ACE 优先。 如果 ACE 引用任何先前确定的 SID,则对于允许访问的 ACE 或带有您的权限掩码的 ACE 掩码,对于拒绝访问的 ACE,从您的权限掩码中屏蔽掉 ACE 的掩码。

处理完所有 ACE 后,您的权限掩码就拥有了答案。

跳过继承的 ACL

在许多目录层次结构中,大多数或所有文件和目录都将从其父级继承其权限。但是,这无济于事。继承的 ACL 可能在父级上未激活,因此子级的有效权限与父级的有效权限不匹配。因此,即使继承了 ACL,仍然需要对其进行检查。

缓存 GetEffectiveRightsFromAcl 的结果

只需创建从 ACL 到有效权限掩码的映射。为此,您需要一种比较 ACL 的方法。您不能只使用 memcmp 比较整个 ACL,因为 ACL.AclSize 包括额外填充的大小。相反,比较 ACE 的数量,如果它们相同,则使用 memcmp 比较各个 ACE。

我在我的Program Files 目录上试过这个。扫描整个目录结构需要 6 次调用 GetEffectiveRightsFromAcl。剩下的 2,708 个目录是从缓存中解析出来的,所以速度要快得多。

以下实现了GetEffectiveRightsFromAcl 的缓存版本。请注意,缺少错误处理,并且它永远不会释放它放入映射中的 PACL。

// Compare two access-control lists.
// Return <0 if acl1<acl2, 0 if acl1==acl2 and >0 if acl1>acl2.
// The ordering is arbitrary but consistent.
int aclcmp(PACL acl1, PACL acl2)

    // First compare by number of ACEs
    int c = acl1->AceCount - acl2->AceCount;
    if (c)
        return c;

    // We have the same number of ACEs, so compare each ACE
    int aceCount = acl1->AceCount;
    for (int aceIndex = 0; aceIndex != aceCount; ++aceIndex)
    
        // Get the ACEs
        PACE_HEADER ace1;
        PACE_HEADER ace2;
        GetAce(acl1, aceIndex, (LPVOID*)&ace1);
        GetAce(acl2, aceIndex, (LPVOID*)&ace2);
        // Compare the ACE sizes
        c = ace1->AceSize - ace2->AceSize;
        if (c)
            return c;

        // Compare the ACE content
        c = memcmp(ace1, ace2, ace1->AceSize);
        if (c)
            return c;
    

    return 0;


// Less-than operator for pointers to ACLs
class ComparePAcl

public:
    bool operator()(const PACL& acl1, const PACL& acl2) const
    
        return aclcmp(acl1, acl2) < 0;
    
;

// Map from pointers-to-ACLs to access masks
typedef std::map<PACL, ACCESS_MASK, ComparePAcl> AclToAccessMask;
AclToAccessMask aclToAccessMask;

// Just to check how the cache performs
DWORD foundCount = 0;
DWORD notFoundCount = 0;

// Same as GetEffectiveRightsFromAcl but caches results.
// Note that this must be called with the same trustee to get meaningful results.
DWORD CachedGetEffectiveRightsFromAcl(PACL pacl, PTRUSTEE pTrustee, PACCESS_MASK pAccessRights)

    AclToAccessMask::const_iterator it = aclToAccessMask.find(pacl);
    if (it != aclToAccessMask.end())
    
        // The ACL is in the cache
        ++foundCount;
        *pAccessRights = it->second;
    
    else
    
        // The ACL is not in the cache
        ++notFoundCount;
        DWORD rc = GetEffectiveRightsFromAcl(pacl, pTrustee, pAccessRights);
        if (rc != ERROR_SUCCESS)
            return rc;
        // TODO: Clean up copies of ACLs afterwards
        PACL aclcopy = (PACL)malloc(pacl->AclSize);
        memcpy(aclcopy, pacl, pacl->AclSize);
        aclToAccessMask.insert(AclToAccessMask::value_type(aclcopy, *pAccessRights));
    

    return ERROR_SUCCESS;

【讨论】:

感谢您的信息。我感谢您的帮助!我用代码示例更新了我的原始帖子。你是这个意思吗? 是的,这就是我的想法。它对您有用吗,即它是否按预期跳过子文件夹? 它确实运行得更快。如果没有继承检查,我会得到大约 7 秒,而有了它 - 大约 1 秒。我试图克服的问题是继承 ACL 的访问掩码。如果是继承的,访问掩码从何而来? 是的,这种方法缺少一些东西。例如,如果我递归搜索我的C:\Program Files 文件夹,它的访问掩码最初是0x1200A9。然后我有C:\Program Files\Far\Addons 文件夹,没有我的继承检查从GetEffectiveRightsFromAcl API 获得0x1F01FF 的访问掩码,但是通过我的继承检查过程它变成inherited,因此它的访问掩码被跳过(尽管它不同) ... 对不起。你建议用继承的 ACL 做一些事情,所以我解释了如何做,但我没有考虑清楚。其实用处不大。我在答案中解释了原因。我还发布了代码(未准备好生产)来实现缓存循环GetEffectiveRightsFromAcl。这在我的计算机上提供了巨大的性能改进,它没有附加到域。如果涉及 DC,我希望改进会更加显着。

以上是关于使用 C++ 递归扫描目录上的 ACL的主要内容,如果未能解决你的问题,请参考以下文章

基于递归 Promise 的目录读取

递归扫描目录内容

异步和递归目录扫描,用于 Nodejs 和 Expressjs 中的文件列表

Redshift 复制命令递归扫描

互联网企业安全高级指南读书笔记之漏洞扫描

互联网企业安全高级指南读书笔记之漏洞扫描