【问题标题】:How to drop elevated privileges when i no longer need them当我不再需要提升权限时如何删除它们
【发布时间】:2017-06-19 14:44:34
【问题描述】:

我有一个应用程序需要在以管理员身份运行时注册控件,我希望该应用程序在不再需要提升权限时删除它们。我已经读到这可以通过 AdjustTokenPrivileges (Dropping privileges in C++ on Windows) 完成,但我还没有找到任何示例代码可以让我从 SECURITY_MANDATORY_HIGH_RID 转到 SECURITY_MANDATORY_MEDIUM_RID。我的代码是使用 Visual Studio 编写的 C++。

【问题讨论】:

  • 正确的做法是在单独的进程中执行需要提升的代码。实现此目的的一个好方法是使用 COM 提升名字对象。
  • 我们可以轻松降级完整性级别 - 系统允许 SetTokenInformationTokenIntegrityLevel 这样做。同样,当您设置的值小于SECURITY_MANDATORY_HIGH_RID 时,系统会自动禁用令牌中的某些权限,并且不能再启用它 - 这是一种方式过程。但您的令牌组将保持不变 - 您仍将是 Administrators 的成员 - 因为此组是强制性的 - 它不能被禁用
  • 在没有 COM 或复杂的安全 API 调用的情况下执行此操作的一种简单方法是在应用程序的清单中指定 asInvoker,并在需要时通过使用 runas 动词调用 ShellExecuteEx() 来提升自己.子进程只会做需要提升和退出的任务。然后原来的进程将继续不提升运行。如果原始进程已经启动,那么 ofc 它可以自己完成这项工作。
  • 感谢您的 cmets,我正在努力支持 Windows 7 和 10

标签: c++ windows winapi


【解决方案1】:

如果你愿意

允许我从 SECURITY_MANDATORY_HIGH_RID 开始的示例代码 SECURITY_MANDATORY_MEDIUM_RID

您需要使用TOKEN_ADJUST_DEFAULT(用于更改完整性级别-这是强制性的)和WRITE_OWNER(用于更改您的令牌安全描述符中的强制性标签-否则您将无法打开自己的进程令牌以写入访问权限)更多 - 但这是可选的)

调用SetTokenInformationTokenIntegrityLevel 让当前的完整性级别降级。在此之后它已经无法提高。

当我们将完整性级别设置为低于SECURITY_MANDATORY_HIGH_RID 时,系统也会在内部禁用令牌中的某些权限。我怀疑这是否已记录在案,但从我的测试来看,下一个权限已被禁用并且无法再启用:

SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeImpersonatePrivilege
SeDelegateSessionUserImpersonatePrivilege

但您仍将是管理员组的成员(S-1-5-32-544 管理员),并且此组不能被 AdjustTokenGroups 禁用,因为函数无法使用 @ 禁用组987654330@ 属性 - 但 S-1-5-32-544 具有此属性。并且更改主进程令牌也是不可能的(如果我们有SeAssignPrimaryTokenPrivilege,只有在进程创建之后(处于挂起状态)和它开始执行(恢复)之前才有可能)

所以在将完整性级别降级到 medium 后,您的应用程序实际上将处于中间状态 - 从一方面您失去了最重要的特权,但对对象(文件、注册表项)的访问主要不是基于特权但组成员身份和强制性标签 - 与完整性级别。因为 Administrators 组仍将在您的令牌中启用,并且大多数对象没有明确的强制标签(默认为中等)-您的应用程序仍然能够打开/创建限制应用程序的文件/键(uac 下的管理员) 不能。但是,如果您将完整性级别降级为SECURITY_MANDATORY_LOW_RID - 您的应用程序确实有限,但大多数遗留代码在低级完整性下工作不正确

降级完整性级别的最少代码:

ULONG SetMediumLevel()
{
    HANDLE hToken;

    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, &hToken))
    {
        ULONG cbSid = GetSidLengthRequired(1);

        TOKEN_MANDATORY_LABEL tml = { { alloca(cbSid)} };

        ULONG dwError = NOERROR;

        if (!CreateWellKnownSid(WinMediumLabelSid, 0, tml.Label.Sid, &cbSid) ||
            !SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
        {
            dwError = GetLastError();
        }

        CloseHandle(hToken);

        return dwError;
    }

    return GetLastError();
}

但这里存在薄弱点 - 令牌本身具有带有显式标签的安全描述符。和高完整性进程具有High Mandatory LabelSYSTEM_MANDATORY_LABEL_NO_WRITE_UP 访问策略。这意味着我们不能再以写访问权限(TOKEN_ADJUST_*)(读访问权限可以)打开我们的进程令牌。如果应用程序在某个地方尝试使用此访问权限打开自我进程令牌,这可能会产生问题(一些糟糕的设计代码可以在需要查询自己的进程令牌属性而不是 TOKEN_QUERY 访问时询问 TOKEN_ALL_ACCESS 并在这一点上失败)。为了防止这个潜在问题,我们可以在降级之前更改令牌安全描述符的强制标签完整性:

ULONG SetMediumLevel()
{
    HANDLE hToken;

    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT|WRITE_OWNER, &hToken))
    {
        ULONG cbSid = GetSidLengthRequired(1);

        TOKEN_MANDATORY_LABEL tml = { { alloca(cbSid)} };

        ULONG dwError = NOERROR;
        if (CreateWellKnownSid(WinMediumLabelSid, 0, tml.Label.Sid, &cbSid))
        {
            SECURITY_DESCRIPTOR sd;
            ULONG cbAcl = sizeof(ACL) + FIELD_OFFSET(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + cbSid;
            PACL Sacl = (PACL)alloca(cbAcl);

            if (!InitializeAcl(Sacl, cbAcl, ACL_REVISION) ||
                !AddMandatoryAce(Sacl, ACL_REVISION, 0, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, tml.Label.Sid) ||
                !InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ||
                !SetSecurityDescriptorSacl(&sd, TRUE, Sacl, FALSE) ||
                !SetKernelObjectSecurity(hToken, LABEL_SECURITY_INFORMATION, &sd) ||
                !SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
            {
                dwError = GetLastError();
            }
        }

        CloseHandle(hToken);

        return dwError;
    }

    return GetLastError();
}

【讨论】:

  • 感谢您的帖子。我是否可以假设仅当我相信我的代码将在提升的权限被删除后尝试使用 TOKEN_ALL_ACCESS 打开令牌时才需要第二个代码示例。
  • @Brentarsky - 是的,如果你的代码尝试使用 TOKEN_ALL_ACCESSGENERIC_WRITE 或任何 TOKEN_ADJUST_* 打开令牌 - 你会被拒绝访问。如何打开读取(查询)令牌。通常第一个代码就足够了,但并不总是
猜你喜欢
  • 2010-10-09
  • 2019-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多