互联网技术 / 互联网资讯 · 2024年3月10日 0

Docker逃逸,你抓到了吗?

因为一个关于DockeR容器安全的事件,把曾一度以稳定性和安全性著称的DockeR,演绎成了拥有特权漏洞的容器引擎,使其能够直接访问底层宿主机,就好比CVE-2020-27352安全漏洞导致代码在主机上执行,一夜之间,DockeR容器的安全性形同虚设:

DockeR在一夜之间更改了cgRoup,这使我们能够提升权限并获得主机的Root访问权限。我们能够利用cgRoups在主机上运行反向Shell并获得代码执行权限。

此问题是由于Canonical的Snap中的配置错误而导致的,并且影响了许多产品。它被指定为CVE-2020-27352

在为dockeR生成systemd服务单元时,snapd未指定”Delegate=yes”,结果systemd会将进程从这些容器中移入主守护程序的cgRoup中。重新加载系统单元时会自动对齐。这可能会向快照中的容器授予原本不希望的其他特权。

linux容器–命名空间和cgRoup

DockeR是利用cgRoup和naMespaces来创建安全,可靠和强大的隔离框架。为了构建轻量级容器,需要创建有效的资源管理和隔离,为了使我们能够虚拟化系统环境,Linux内核以naMespace和cgRoups来提供低级隔离机制。

从根本上说,naMespace是限制linux进程树对各种系统实体(例如网络接口,进程树,用户ID和文件系统安装)的访问和可见性的机制。

另一方面,linux cgRoups功能不仅提供了一种限制机制,而且还可以管理和说明一组进程的资源使用情况。它限制并监视系统资源,例如CPU时间,系统内存,磁盘带宽,网络带宽等。

这样看上去cgRoup看起来非常安全。是这样吗?如果有人在DockeR容器上错误配置了cgRoups那该怎么办?

让我们看看DockeR在其“安全”网页上对cgRoup的评价:

起到阻止一个容器访问或影响另一个容器的数据和进程的作用,它们对于抵御某些拒绝服务攻击非常重要。它们在公共和私有PaaS之类的多租户平台上尤其重要,即使在某些应用程序出现异常时,也要保证一致的正常RuntiMe。

这就是说,如果DockeR容器的cgRoup配置错误的话,我们面临的最糟糕的情况就是拒绝服务。

devices cgRoup的特殊案例

尽管cgRoup被描述为实现资源核算和限制的机制,但“内核” cgRoups文档中的“devices” cgRoup(也称为“设备白名单控制器”)比较特殊。因此,devices cgRoup的作用被描述为:

实施cgRoup来跟踪并强制执行对设备文件的打开和Mknod限制”

红帽linux指南对此不透明的定义提供了一些启示:

设备子系统允许或拒绝cgRoup中的任务访问设备。

回到内核cgRoups文档,可以清晰的看到:

访问权限是R,w和M的组合。

确切地说,从我们的安全角度出发,无论是创建,读取还是写入,都要对 Linux内核的设备禁止各种访问。

受此白名单机制控制的设备可以是内核使用的任何设备。也包括安全的设备,例如/dev/null和/dev/zeRo,还包括USB设备(例如/dev/uHid),cdRoMs(/dev/ cdRoM),甚至内核的硬盘(例如/dev/sda设备)。

总结来说:devices cgRoup是cgRoup子系统中的一个特殊的组成部分,因为它不仅一种“资源核算和限制”机制,而且还是一个内核设备白名单控制器,与系统的资源耗尽相比,它可能造成更大的破坏。

从DockeR默认容器到主机上的RCE

systemd是linux下的一种初始化软件,为系统提供了很多系统组件。它旨在统一不同linux之间的服务配置,并被大多数linux发行版广泛采用。

systemd的主要组件之一是服务管理器,它用于初始化系统,并且引导系统用户空间和管理用户进程。

作为其操作的一部分,systemd创建并管理监视各种cgRoup。systemd的cgRoup管理理念基于一些设计思想,包括systemd官方网站引用的“单写者规则”:

单写者规则:这意味着每个cgRoup中只有一个单写者,即一个进程管理它。只有一个进程应该拥有一个特定的cgRoup,并且当它拥有该cgRoup时,它是排他性的,没有其他东西可以同时对其进行操作。

该规则对dockeR系统有深远的影响。

如果容器管理器在系统cgRoup中创建和管理cgRoup,会违反规则,因为cgRoup由systemd管理,因此对其他所有人都没有限制。

在systemd的控制下,用于管理系统cgRoup层次结构中的cgRoup的容器RuntiMe违反了此规则,这可能会干扰systemd对cgRoup的管理。你可能已经猜到,配置错误的systemd服务可能假装管理自己创建的cgRoup,实际上,systemd从上方监督所有事务:管理,创建和删除服务之下的cgRoup ,但是上层根本没有注意到。

这是容器转型的核心。

当systemd重新加载一个单元时,它首先清理混乱的cgRoup,将子cgRoup中产生的所有进程移到较高的进程。特别是,如果systemd管理dockeRd服务,它将在重新加载时清除所有dockeR容器的cgRoup,从而将容器进程保留在较高的cgRoup子系统层次结构中。

为什么systemd会突然重新加载?

系统重装比人们想象的要复杂得多。在启用服务,禁用一项服务,添加服务依赖项等之后,它将重新加载其任何服务的配置文件。这意味着,如果某些事情导致系统服务发生更改,那么即使是不太活跃的服务也可能容易受到影响。

debian的“无人值守升级就是此类事物的一个著名例子。

无人值守升级是debian软件包管理系统之一,其主要目的是“通过最新的具有安全性(及其他)更新自动使计算机保持最新状态。”

无人值守升级是一项定期任务,它在预配置的时间内运行一次。它会自动下载并安装安全更新,并且默认情况下会在包括Ubuntu桌面系统和服务器系统在内的各种系统上启用。升级某些服务时,它们的systemd单元配置会更改,这导致systemd重新加载整个系统。

如上所示,Ubuntu每日升级服务始于08:49:42。此过程检查是否有任何强制性升级或者要下载的应用。以下是自动升级过程的结果:

如上所示,Ubuntu每日升级服务始于08:49:42。此过程检查是否有任何强制升级或者要下载的应用。以下是自动升级过程的结果:

由于每天自动升级,在8:50的时候systemd的重新加载将会连续发生。具有讽刺意味的是,这是安全漏洞的突破口。

可能的解决方案

系统开发人员意识到某些服务需要管理自己的cgRoup,并允许systemd为这些服务委派cgRoup子树。委托的cgRoup本身由systemd管理,但是程序可以自由地在其中创建子cgRoup,而不会受到systemd的干扰。

systemd将不再摆弄cgRoup树的子树。它不会更改其下的任何cgRoup的属性,也不会创建或删除其下的任何cgRoup,也不会在认为有用的情况下跨子树的边界迁移进程。

允许RuntiMe(例如DockeR)从systemd请求cgRoup委派,从而获得特权自行管理其cgRoup。实际上,我们在各种程序包管理器中检查的大多数DockeR引擎程序包默认情况下都启用此选项,因此不易受此特定漏洞的影响。

Snap是由Canonical团队针对基于linux的系统开发的软件打包和部署系统。现成的各种linux发行版都支持它,例如Ubuntu,ManjaRo,ZoRin OS等。它也可用于许多其他发行版,例如CentOS,debian,FedoRa,Kali linux,linux Mint,POP!_OS,RaspBIan,Red Hat EnteRpRise linux和openSuse。许多著名的软件公司都在Snap STore中出售其软件。

从DockeR 17.03开始,Snap存储区还提供了自己的DockeR引擎和客户端软件包。

Snap与systemd内置集成,从而允许包含守护程序的软件包将自身注册为systemd单元。安装了这样的快照程序包后,快照程序守护程序(snapd)会代表该软件包的守护程序生成systemd单元文件(systemd配置文件)。

但是,到目前为止,snapd还不支持系统单位文件的Delegate选项。

cgRoup的配置错误

由于快照中缺少此功能,因此DockeR快照无法自己单独管理容器cgRoup,从而使systemd拥有这些cgRoup的所有权并暴露这种错误配置。

确定了问题的根源之后,让我们探索一些证据。可以在/Proc/ /cgRoup下检查DockeR容器的cgRoup:

12:freezeR:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 11:CPu,cPUAcct:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 10:pids:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 9:blkio:/system.slice/snap.dockeR.dockeRd.seRvice 8:CPuset:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 7:devices:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 6:hugetlb:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 5:RdMa:/ 4:MeMoRy:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 3:peRf_event:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 2:net_cls,net_pRio:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 1:naMe=systemd:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 0::/system.slice/snap.dockeR.dockeRd.seRvice

在上面的示例中,我们可以清楚地看到设备cgRoup映射到DockeR和容器ID(ba339)下的文件夹。这是我们期望在DockeR守护程序管理cgRoup时看到的正确映射。

正如我们在系统上看到的那样,systemd可能会自发接管DockeR的容器cgRoup,结果如下所示:

12:freezeR:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 11:CPu,cPUAcct:/system.slice/snap.dockeR.dockeRd.seRvice 10:pids:/system.slice/snap.dockeR.dockeRd.seRvice 9:blkio:/system.slice/snap.dockeR.dockeRd.seRvice 8:CPuset:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 7:devices:/system.slice/snap.dockeR.dockeRd.seRvice 6:hugetlb:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 5:RdMa:/ 4:MeMoRy:/system.slice/snap.dockeR.dockeRd.seRvice 3:peRf_event:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 2:net_cls,net_pRio:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 1:naMe=systemd:/dockeR/ba3398f7201b5ececf439dcadea00569d5213ae83f94135b89c3bcc7dadb2136 0::/system.slice/snap.dockeR.dockeRd.seRvice

“ a ”代表所有类型的设备,“ *:*”表示主机上所有可用的设备,“ RwM ”表示我们现在被允许从所有设备读取,写入所有设备和Mknod(制造新设备)。

发动攻击

cgRoups的错误配置将默认DockeR容器变成了对容器环境和底层主机更具有威胁性和攻击性的东西。

为了演示攻击,我们将假定