Windows利用任意对象目录创建来提权

admin 2024年6月3日16:43:49评论27 views字数 7171阅读23分54秒阅读模式

James Forshaw 来自 Google 的 Project Zero 团队,他在这篇博客文章中详细说明了如何利用一个特定的 Windows 系统漏洞 Issue 1550。这个漏洞允许攻击者在以 SYSTEM 权限运行时,在用户可控制的位置创建一个任意的对象目录。Forshaw 希望通过详细说明如何利用这个特定的漏洞,让读者更好地理解 Windows 操作系统的复杂性,并向微软提供非内存破坏性漏洞利用技术的信息,以便他们能够以某种方式缓解这些漏洞。

漏洞概述

对象管理器目录与普通文件目录无关。这些目录是使用一组独立的系统调用来创建和操作的,例如使用 NtCreateDirectoryObject 而不是 NtCreateFile。尽管它们不是文件目录,但它们容易受到许多与文件系统相同的问题类别的影响,包括特权创建和符号链接植入攻击。

Issue 1550 是一个漏洞,它允许在以 SYSTEM 权限运行时,在用户可控制的位置创建目录。这个错误的根源在于 Desktop Bridge 应用程序的创建过程中。负责创建新应用程序的 AppInfo 服务调用了未公开的 API CreateAppContainerToken 来进行一些内部维护工作。不幸的是,这个 API 在用户的 AppContainerNamedObjects 对象目录下创建对象目录,以支持操作系统重定向 BaseNamedObjects 和 RPC 端点。

由于调用 API 时没有模拟用户(它通常在 CreateProcess 中调用,通常问题不大),对象目录是以服务的身份创建的,即 SYSTEM。由于用户可以在他们的 AppContainerNamedObjects 目录中写入任意对象,他们可以放置一个对象管理器符号链接,并将目录创建重定向到对象管理器命名空间的几乎任何位置。作为一个额外的好处,目录是以明确的安全描述符创建的,这在利用时将变得非常重要。

利用这个漏洞的一个困难是,如果对象目录没有在 AppContainerNamedObjects 下创建,因为我们已经重定向了它的位置,那么执行令牌创建并作为其操作的一部分捕获目录句柄的底层 NtCreateLowBoxToken 系统调用将会失败。目录将被创建,但几乎立即被删除。这种行为实际上是由于我之前报告的一个早期问题 483 改变了系统调用的行为。这仍然可以通过在目录被删除前打开一个句柄来利用,并且实际上,只要系统具有多个处理器(这基本上是任何现代系统),赢得这场竞赛就是可靠的。拥有一个打开的句柄,目录就会保持活动状态,直到利用所需的时间。

这就是我发送给 MSRC 的原始 PoC 结束的地方,所有的 PoC 只是创建了一个任意的对象目录。你可以在问题跟踪器中的初始错误报告中找到这个 PoC。现在,让我们深入了解我们如何可能利用这个漏洞,从一个普通用户账户升级到一个特权 SYSTEM 账户。

利用

利用的主要问题是找到一个我们可以创建对象目录的位置,然后可以利用这个位置来提升我们的权限。事实证明,这比你想象的要困难。虽然几乎所有的Windows应用程序都在背后使用对象目录,例如BaseNamedObjects,但应用程序通常与漏洞无法修改的现有目录交互。

一个有趣的可被滥用的对象目录是KnownDlls(在我之前的博客中简要提到过)。这个对象目录包含了一个命名的图像节对象列表,形式为NAME.DLL。当应用程序在SYSTEM32目录中调用LoadLibrary加载DLL时,加载器首先检查KnownDlls对象目录中是否存在现有的图像节,如果存在,则将加载该节而不是创建一个新的节对象。

Windows利用任意对象目录创建来提权

KnownDlls仅限于可由管理员写入(并非严格意义上如此,正如我们将看到的),因为如果你可以在该目录中放置一个任意的节对象,你可以强制一个系统服务加载命名的DLL,例如使用我在上一篇博客文章中描述的Diagnostics Hub服务,它将映射该节,而不是磁盘上的文件。然而,这个漏洞不能用来修改KnownDlls对象目录,除了添加一个新的子目录,这在利用中并没有帮助。也许我们可以通过滥用我们漏洞可以利用的其他功能来间接针对KnownDlls?

每当我研究产品的特定领域时,我总是记录下有趣或意外的行为。当我研究Windows符号链接时,我发现了一个有趣的行为。Win32 API支持一个叫做DefineDosDevice的函数,这个API的目的是允许用户定义一个新的DOS驱动器字母。该API接受三个参数:一组标志、要创建的驱动器前缀(例如X:)和要映射的目标设备。API的主要用途在于像CMD的SUBST命令这样的事物中。

在现代版本的Windows中,这个API在用户自己的DOS设备对象目录内创建了一个对象管理器符号链接,这是一个普通低权限用户帐户可以写入的位置。然而,如果你查看DefineDosDevice的实现,你会发现它不是在调用者的进程中实现的。相反,实现调用了当前会话的CSRSS服务内的RPC方法,具体是BASESRV.DLL内的BaseSrvDefineDosDevice方法。调用特权服务的主要原因是它允许用户创建一个永久性的符号链接,当所有符号链接对象的句柄都被关闭时,它不会被删除。通常,要创建一个永久性的命名内核对象,你需要SeCreatePermanentPrivilege权限,但普通用户没有这个权限。另一方面,CSRSS有,所以通过调用该服务,我们可以创建永久性的符号链接。

能够创建永久性的符号链接当然很有趣,但如果我们仅限于在用户的DOS设备目录中创建驱动器字母,它就不会特别有用。我还注意到实现从未验证lpDeviceName参数是一个驱动器字母。例如,你可以指定一个名为“GLOBALROOTRPC ControlABC”的名称,它实际上会在用户的DosDevices目录之外创建一个符号链接,具体来说,在这种情况下的路径是“RPC ControlABC”。这是因为实现将DosDevice前缀“??”附加到设备名称并传递给NtCreateSymbolicLink。内核将跟随完整路径,找到GLOBALROOT这是一个特殊的符号链接,返回到根目录,然后跟随路径创建任意的对象。不清楚这是否是有意的行为,所以我更深入地查看了CSRSS中的实现,下面以简化的形式展示。

NTSTATUS BaseSrvDefineDosDevice(DWORD dwFlags,
                               LPCWSTR lpDeviceName,
                               LPCWSTR lpTargetPath) {
   WCHAR device_name[];
   snwprintf_s(device_name, L"\??\%s", lpDeviceName);
   UNICODE_STRING device_name_ustr;
   OBJECT_ATTRIBUTES objattr;
   RtlInitUnicodeString(&device_name_ustr, device_name);
   InitializeObjectAttributes(&objattr, &device_name_ustr,
                              OBJ_CASE_INSENSITIVE);

   BOOLEAN enable_impersonation = TRUE;
   CsrImpersonateClient();
   HANDLE handle;
   NTSTATUS status = NtOpenSymbolicLinkObject(&handle, DELETE, &objattr);①
   CsrRevertToSelf();

   if (NT_SUCCESS(status)) {
       BOOLEAN is_global = FALSE;

       // Check if we opened a global symbolic link.
       IsGlobalSymbolicLink(handle, &is_global); ②
       if (is_global) {
           enable_impersonation = FALSE; ③
           snwprintf_s(device_name, L"\GLOBAL??\%s", lpDeviceName);
           RtlInitUnicodeString(&device_name_ustr, device_name);
       }

       // Delete the existing symbolic link.
       NtMakeTemporaryObject(handle);
       NtClose(handle);
   }

   if (enable_impersonation) { ④
       CsrRevertToSelf();
   }

   // Create the symbolic link.
   UNICODE_STRING target_name_ustr;
   RtlInitUnicodeString(&target_name_ustr, lpTargetPath);

   status = NtCreateSymbolicLinkObject(&handle, MAXIMUM_ALLOWED,
                               objattr, target_name_ustr); ⑤

   if (enable_impersonation) { ⑥
       CsrRevertToSelf();
   }
   if (NT_SUCCESS(status)) {
       status = NtMakePermanentObject(handle); ⑦
       NtClose(handle);
   }
   return status;
}
}

我们可以看到这里代码做的第一件事是构建设备名称路径,然后尝试以 DELETE 访问权限打开符号链接对象①。这是因为该 API 支持重新定义一个现有的符号链接,所以它必须首先尝试删除旧的链接。如果我们按照默认路径进行,链接不存在,我们将看到代码模拟调用者(在这种情况下是低权限用户)④,然后创建符号链接对象⑤,恢复模拟⑥,并使对象永久化⑦,然后返回操作的状态。没什么令人惊讶的,我们可以明白为什么我们可以创建任意的符号链接,因为所有代码做的就是用“??”前缀传递的设备名称。由于代码在执行任何重要操作时都会模拟调用者,我们只能在用户已经可以写入的位置创建链接。

更有趣的是中间的条件,目标符号链接为 DELETE 访问权限打开,这是调用 NtMakeTemporaryObject 所需的。打开的句柄传递给另一个函数②,IsGlobalSymbolicLink,并根据该函数的结果设置禁用模拟的标志,并再次使用全局 DOS 设备位置 GLOBAL?? 作为前缀重新创建设备名称③。IsGlobalSymbolicLink 做了什么?我们可以再次反编译该函数并进行检查。

void IsGlobalSymbolicLink(HANDLE handle, BOOLEAN* is_global) {
   BYTE buffer[0x1000];
   NtQueryObject(handle, ObjectNameInformation, buffer, sizeof(buffer));
   UNICODE_STRING prefix;
   RtlInitUnicodeString(&prefix, L"\GLOBAL??\");
   // Check if object name starts with GLOBAL??
   *is_global = RtlPrefixUnicodeString(&prefix, (PUNICODE_STRING)buffer);
}

代码检查打开对象的名称是否以 GLOBAL?? 开头。如果是,它将 is_global 标志设置为 TRUE。这将导致清除启用模拟的标志并重写设备名称。这意味着如果调用者具有全局 DOS 设备目录内符号链接的 DELETE 访问权限,那么符号链接将被重新创建,没有任何模拟,这意味着它将被创建为 SYSTEM 用户。这本身听起来不是特别有趣,因为默认情况下,只有管理员才能为 DELETE 访问权限打开全局符号链接之一。然而,如果我们可以在全局 DOS 设备目录下创建一个子目录,该目录可以由低权限用户写入呢?该目录中的任何符号链接都可以被低权限用户打开以进行 DELETE 访问,因为用户可以指定任何访问权限,代码将将链接标记为全局的,而实际上情况并非如此,禁用模拟并将其重新创建为 SYSTEM。你猜怎么着,我们有一个漏洞,允许我们在全局 DOS 设备目录下创建任意的对象目录。

如果没有路径重写,这可能不会很有利用价值。我们可以利用路径“??ABC”与“GLOBAL??ABC”不相同的事实,构建一个机制,以 SYSTEM 的身份在对象管理器命名空间的任何位置创建任意的符号链接。这对我们有什么帮助?如果你向 KnownDlls 写入一个符号链接,那么当 DLL 加载器请求一个节时,内核将遵循这个链接。因此,尽管我们不能直接在 KnownDlls 内创建一个新的节对象,我们可以创建一个指向该目录外部的符号链接,指向低权限用户可以创建节对象的位置。现在我们可以滥用这个劫持,在特权进程中加载任意的 DLL 并实现权限提升。

我们可以通过以下步骤利用我们的漏洞:

  1. 利用漏洞创建目录“GLOBAL??KnownDlls”

  2. 在新目录中创建一个符号链接,名称为要劫持的DLL,例如TAPI32.DLL。这个链接的目标并不重要。

  3. 在用户的DOS设备目录中创建一个名为“GLOBALROOT”的新符号链接,指向“GLOBAL??”。这将覆盖真实GLOBALROOT符号链接对象,当调用者通过用户的DOS设备目录访问它时。

  4. 调用DefineDosDevice,指定设备名为“GLOBALROOTKnownDllsTAPI32.DLL”,目标路径为用户可以在内部创建节对象的位置。这将导致以下操作:

    a. CSRSS打开符号链接“??GLOBALROOTKnownDllsTAPI32.DLL”,这导致打开“GLOBAL??KnownDllsTAPI32.DLL”。由于这由用户控制,打开成功,并且链接被视为全局的,这禁用了模拟。

    b. CSRSS重写路径为“GLOBAL??GLOBALROOTKnownDllsTAPI32.DLL”,然后以非模拟状态调用NtCreateSymbolicLinkObject。这导致跟随真实的GLOBALROOT链接,结果在创建符号链接“KnownDllsTAPI32.DLL”时带有任意目标路径。

  5. 在目标位置为目标DLL创建图像节对象,然后通过让服务调用LoadLibrary加载TAPI32.DLL的路径,强制将其加载到特权服务中,例如Diagnostics Hub。

  6. 权限提升实现。

滥用DefineDosDevice API实际上还有第二个用途,它是从管理员到受保护的进程轻量级(PPL)的绕过。PPL进程仍然使用KnownDlls,所以如果你可以添加新条目,你可以向受保护的进程注入代码。为了防止这种攻击向量,Windows在KnownDlls目录上标记了进程信任标签,它阻止除了最高级别的PPL进程之外的所有进程写入它,如下所示。

Windows利用任意对象目录创建来提权

我们的漏洞利用是如何工作的?CSRSS实际上以最高级别的PPL运行,因此被允许写入KnownDlls目录。一旦模拟被丢弃,将使用进程的身份,这将允许完全访问。

如果你想测试这个漏洞利用,我已经在问题跟踪器这里附上了新的PoC。

总结

此时你可能会想知道我是否向MSRC报告了DefineDosDevice的行为?我没有,主要是因为它本身不是一个漏洞。即使在管理员到PPL的情况下,MSRC也不认为这是一个可服务的安全边界(例子)。当然,Windows开发人员可能会选择在未来改变这种行为,假设它不会导致兼容性方面的重大回归。这个函数自Windows早期以来就已经存在,自至少Windows XP以来就存在当前的行为,所以可能有一些东西依赖于它。通过详细描述这种漏洞利用,我想给MS提供足够的信息,以便在未来解决这种技术。

我确实向MSRC报告了这个漏洞,它在2018年6月的补丁中被修复。微软是如何修复这个漏洞的?开发人员添加了一个新的API,CreateAppContainerTokenForUser,在创建新的AppContainer令牌期间模拟令牌。通过在令牌创建期间进行模拟,代码确保所有对象仅以用户的权限创建。由于这是一个新API,现有代码需要更改才能使用它,因此你可能仍然可以找到使用旧CreateAppContainerToken的代码,以一种易受攻击的模式。

在任何平台上利用漏洞有时需要对不同组件如何交互有相当深入的了解。在这种情况下,虽然最初的漏洞显然是一个安全问题,但不清楚你如何进行完整的利用。在逆向工程过程中,即使某件事本身不是一个安全漏洞,但可能是利用另一个漏洞的有用信息,所以始终值得记录有趣的行为日志。

原文始发于微信公众号(3072):Windows利用任意对象目录创建来提权

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月3日16:43:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows利用任意对象目录创建来提权https://cn-sec.com/archives/2810188.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息